Struts2核心技术 (二)

struts2中的参数封装

静态参数封装

什么是静态参数? 
静态参数就是硬编码的,不可随意改变。

例子

        我们首先创建一个Action类,里面有两个参数,用来封装请求参数

        public class User extends ActionSupport {
            private String username;    //用户名
            private int age;           //年龄

            public String getUsername() {
                return username;
            }

            public void setUsername(String username) {
                this.username = username;
            }

            public int getAge() {
                return age;
            }

            public void setAge(int age) {
                this.age = age;
            }

            public String adduser(){ 
                System.out.println(username+":"+age);
                return null;
            }
        } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
        我们在struts.xml中配置静态参数  

            <package name="p1" extends="struts-default">
                <action name="action1" class="com.cad.struts2.User"  method="adduser"> 
                    <!--通过注入的方式封装静态参数-->
                    <param name="username">张三</param>
                    <param name="age">18</param>
                </action>
            </package>  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
    我们访问action1动作时,会输出 张三:18  
  • 1
  • 2

我们配置的静态参数怎么就被传递到动作类里呢?

        我们在struts2的运行流程中提到过,我们动作类的动作方法执行之前,会有一系列的拦截器帮我们执行一些操作。 

        在struts-default.xml中,有很多拦截器,拦截器又分为很多拦截器栈,我们默认是使用defaultStack的拦截器栈。  

        这个拦截器栈中有一个叫做staticParams的拦截器帮我们完成静态参数的封装,将配置的静态方法通过action类中的set方法设置进去。 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

动态参数封装

什么是动态参数?

像我们用表单提交的数据,就是动态数据。数据是可变的。 
  • 1
  • 2

例子:

        第一步:我们先创建一个添加用户的表单 

        <form action="${pageContext.request.contextPath }/action1" method="post">
            用户名:<input type="text" name="username"><br>
            年 龄:<input type="text" name="age"><br>
            <input type="submit" value="提交">
        </form> 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
        第一种方式:数据参数和动作类写在一起 

        要求是表单中的name必须和我们类中的set方法后面的名称一致,例如表单中的username对应类中的setUsername,和参数并没有太大关系,和set方法有关系。
                public class User extends ActionSupport {
                    private String username;
                    private int age;

                    public String getUsername() {
                        return username;
                    }

                    public void setUsername(String username) {
                        this.username = username;
                    }

                    public int getAge() {
                        return age;
                    }

                    public void setAge(int age) {
                        this.age = age;
                    }

                    public String adduser(){ 
                        System.out.println(username+":"+age);
                        return null;
                    }
                }  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
        配置struts.xml文件 
        <package name="p1" extends="struts-default">

            <action name="action1" class="com.cad.struts2.User"  method="adduser">

            </action>
        </package>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

我们访问添加用户页面,添加数据,会输出我们添加的数据。

这一系列操作是由拦截器栈中的名为params的拦截器帮我们完成的

上面这种方式将参数和动作类写在一起,看起来太过混乱,我们能不能用一个JavaBean将参数给封装起来。 答案是能。

    第二种方式,将参数数据和动作类分开写  

                第一步,我们创建一个javaBean,User类,用来封装请求参数 
                public class User  implements Serializable{
                    private String username;
                    private int age;

                    public String getUsername() {
                        return username;
                    }

                    public void setUsername(String username) {
                        this.username = username;
                    }

                    public int getAge() {
                        return age;
                    }

                    public void setAge(int age) {
                        this.age = age;
                    }
                } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
                第二步:创建一个添加用户的动作类,里面有一个user对象 
                public class AdduserAction extends ActionSupport {
                    private User user;

                    public User getUser() {
                        return user;
                    }

                    public void setUser(User user) {
                        this.user = user;
                    }
                    public String adduser(){
                        System.out.println(user.getUsername()+":"+user.getAge());
                        return null;
                    }

                } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
        第三步:我们得改变一些我们的jsp页面的写法,因为表单的name要和类中的数据相对应,但类中只有一个user ,username去找动作类中的setUsername,肯定找不到。

            我们这样写,他就会先找类中的user对象,然后去找user对象的username和age
                <body>
                    <form action="${pageContext.request.contextPath }/action1" method="post">
                        用户名:<input type="text" name="user.username"><br>
                        年 龄:<input type="text" name="user.age"><br>
                        <input type="submit" value="提交">
                    </form>
                </body>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

原理:

这一系列操作还是拦截器params帮我们完成。我们在getUser和setUser里打印两句话来看看他到底是怎么执行的
                public User getUser() {
                    System.out.println("getuser");
                    return user;
                }

                public void setUser(User user) { 
                    System.out.println("setuser");
                    this.user = user;
                } 

                输出
                getuser   :先判断对象是否存在
                setuser   :如果判断不存在,调用set方法,通过反射创建一个对象,并且设置给该类   
                getuser   :然后再获取该对象,调用该对象的get和set方法对参数赋值 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

模型驱动和属性驱动

出于结构清晰的考虑,应该采用单独的Model实例来封装请求参数和处理结果,这就是所谓的模型驱动, 

 所谓模型驱动,就是使用单独的JavaBean来贯穿整个MVC流程。 

 所谓属性驱动,就是使用属性来作为贯穿MVC流程的信息携带者,当然属性必须依附于对象,这个对象就是Action实例。 

 简单说,模型驱动就是使用单独的javaBean封装请求参数。
        属性驱动就是把属性写在Action类中。 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

我们发现上面的jsp中的name必须前面得加 user.username。。太过麻烦。我们使用模型驱动来解决这个问题。实际开发中使用这种方式

        模型驱动的要求:
              1.动作类实现ModelDriven接口
              2.实现接口中的getModel方法,返回我们的数据对象 
              3.数据模型对象必须由我们实例化。 

              我们改进动作类 

                public class AdduserAction extends ActionSupport implements ModelDriven<User>{
                    //数据模型对象由我们实例化
                    private User user=new User();

                    public User getUser() {
                        System.out.println("getuser");
                        return user;
                    }

                    public void setUser(User user) { 
                        System.out.println("setuser");
                        this.user = user;
                    }
                    public String adduser(){ 
                        System.out.println(user.getUsername()+":"+user.getAge());
                        return null;
                    }
                    //实现接口方法,返回我们的数据模型对象
                    public User getModel() {
                        // TODO Auto-generated method stub
                        return user;
                    }

                } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
        我们在jsp上就能像以前那样 ,name只用我们的参数相同即可 

        <body>
            <form action="${pageContext.request.contextPath }/action1" method="post">
                用户名:<input type="text" name="username"><br>
                年 龄:<input type="text" name="age"><br>
                <input type="submit" value="提交">
            </form>
        </body> 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

原理:

    其实这时候表单的name已经不仅仅是一个简单的字符串了。 

    这一系列的操作是由ModelDriven和params拦截器帮我们做的 。 

    params拦截器负责提取请求参数,如果是属性驱动模式,则还负责将请求参数传给Action类的属性 

    模型驱动的话就只提取请求参数。 

    ModelDriven拦截器会先判断我们的动作类是否属于ModelDriven类型  

    属于的话,就调用我们实现的getModel方法,获取我们传入的对象 

    然后将我们的对象给压入栈中  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

struts2注册案例

    第一步:我们先创建数据库表 

        create database demo;
        use demo;

        create table user(
            username varchar(100) primary key,
            password varchar(100),
            birthday date,
            hobby varchar(255),
            married boolean
        ); 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
    第二步:在domain包创建我们的user实体类 

        public class User {
            private String username;//用户名
            private String password;//密码
            private Date birthday; //生日
            private String hobby; //爱好
            private boolean married; //是否结婚
            public String getUsername() {
                return username;
            }
            public void setUsername(String username) {
                this.username = username;
            }
            public String getPassword() {
                return password;
            }
            public void setPassword(String password) {
                this.password = password;
            }
            public Date getBirthday() {
                return birthday;
            }
            public void setBirthday(Date birthday) {
                this.birthday = birthday;
            }
            public String getHobby() {
                return hobby;
            }
            public void setHobby(String hobby) {
                this.hobby = hobby;
            }
            public boolean isMarried() {
                return married;
            }
            public void setMarried(boolean married) {
                this.married = married;
            }

        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
        第三步:完成数据层,我们在Dao层处理数据  

        public class UserDao {
            QueryRunner qr=new QueryRunner(JdbcUtils.getDataSource()); 

            //根据名字查找用户
            public User findUserByUsername(String username){
                try {
                    String sql="select * from user where username=?";
                    return qr.query(sql, new BeanHandler<User>(User.class),username);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }

            //添加用户
            public int addUser(User user){
                String sql="insert into user values(?,?,?,?,?)";
                Object []params={user.getUsername(),user.getPassword(),user.getBirthday(),user.getHobby(),user.isMarried()};
                try {
                    return qr.update(sql,params);
                } catch (SQLException e) {
                    throw new RuntimeException(e);
                }
            }

        } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
        第四步:完成业务逻辑层的编写,service层 

        public class UserService {
            private UserDao userdao=new UserDao(); 
            //注册
            public int regist(User user){
                return userdao.addUser(user);
            }
            //通过用户名查找用户
            public User findByUsername(String username){
                return userdao.findUserByUsername(username);
            }
        } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
    第五步:创建web层,我们创建web层动作类  

    public class UserAction extends ActionSupport implements ModelDriven{
        private User user=new User();
        private UserService userservice=new UserService();  

        //注册方法
        public String regist(){ 
            User _user=userservice.findByUsername(user.getUsername());  
            //判断用户是否存在,存在返回exists字符串
            if(_user!=null){
                return "exists";
            } 
            //获取注册成功更新的行数 
            int count=userservice.regist(user); 
            //如果>0,返回success 
            if(count>0){
            return SUCCESS;
            }
            return null;
        }

        public Object getModel() {
            // TODO Auto-generated method stub
            return user;
        }

        public User getUser() {
            return user;
        }

        public void setUser(User user) {
            this.user = user;
        }

    } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
    第六步:我们编写注册的jsp页面 

    <body>
        <form action="${pageContext.request.contextPath }/regist.action" method="post">
            用户名:<input type="text" name="username"><br>
            密   码:<input type="password" name="password"><br>
            生   日:<input type="text" name="birthday"><br>
            爱好:<input type="checkbox" name="hobby" value="篮球">篮球
                <input type="checkbox" name="hobby" value="足球">足球
                <input type="checkbox" name="hobby" value="写代码">写代码<br>
            已婚:<input type="checkbox" name="married" value="true"><br>
            <input type="submit" name="注册"><br>
        </form>

    </body> 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
        第七步:配置 struts.xml 

        <package name="p1" extends="struts-default">

            <action name="regist" class="com.cad.web.action.UserAction"  method="regist">
                <result name="success">/success.jsp</result>
                <result name="exists">/msg.jsp</result>
            </action>
        </package> 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

struts2的异常处理机制

成熟的MVC框架应该提供成熟的异常处理机制。当然可以在方法中手动捕捉异常,当捕捉到特定异常时,返回特定逻辑视图名。

这种方式非常繁琐,需要在方法中写大量try catch块,最大的缺点还是一旦需要改变异常处理方法时,需要修改代码。

最好的方式是通过声明式的方式管理异常处理。struts2提供了一种声明式的异常处理方式。

struts2的异常处理

    我们看Action接口中的execute方法声明。
    public String execute() throws Exception 

    这就意味着我们重写该方法时,无需进行任何异常处理,而是把异常抛给struts2框架处理. 

    struts2框架接收到Action抛出的异常后,根据struts.xml文件配置的异常映射,转入指定的视图资源。

    异常映射功能是由 exception的拦截器帮我们做的。

    struts2的异常处理机制是通过在struts.xml中配置<exception-mapping..../>元素完成。
        属性:
            exception:异常类型
            result:出现该异常时,系统转入result指定的结果 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

局部异常映射和全局异常映射

全局异常映射对所有的Action都有效,但局部异常映射只对该异常映射所在的Action有效。 
如果全局异常映射和局部异常映射配置了同一个异常类型,在该Action内,局部覆盖全局。

            局部异常映射:将<exception-mapping..../>元素作为<action.../>的子元素配置 

            全局异常映射:将<exception-mapping..../>元素作为<global-exception-mappings>元素的子元素配置  
  • 1
  • 2
  • 3
  • 4

异常处理案例

        我们做一个简单的登陆应用 

        第一步:我们编写我们的Action类 

        public class LoginAction implements Action{
            private String username;
            private String password;
            public String getUsername() {
                return username;
            }
            public void setUsername(String username) {
                this.username = username;
            }
            public String getPassword() {
                return password;
            }
            public void setPassword(String password) {
                this.password = password;
            }
            public String execute()throws Exception{ 
                //当用户名为monster时,抛出我们的自定义异常
                if(getUsername().equals("monster")){
                    throw new MyException("自定义异常");
                } 
                //当用户名为sql时,抛出sql异常
                if(getUsername().equalsIgnoreCase("sql")){
                    throw new SQLException("用户名不能为sql");
                }
                if(getUsername().equals("cad")&&getPassword().equals("123456")){
                    return "success";
                }else
                {
                    return "error";
                }
            }

        } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
        第二步:我们编写我们的登陆jsp页面  

        <body>
            <form action="${pageContext.request.contextPath }/login" method="post">
                用户名:<input type="text" name="username"><br>
                密  码:<input type="password" name="password"><br>
                <input type="submit" value="提交">
            </form>
        </body> 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
    第三步:我们编写我们的配置文件struts.xml  

    <package name="p2" extends="struts-default">
        //全局结果视图定义
        <global-results>
            <result name="sql">/exception.jsp</result>
        </global-results>

        //全局异常定义
        <global-exception-mappings>
            //出现sql异常,就转入到视图名为sql的视图结果
            <exception-mapping result="sql" exception="java.sql.Exception"></exception-mapping>
        </global-exception-mappings> 

        <action name="login" class="com.cad.struts2.LoginAction" > 
                //局部异常定义
                <exception-mapping result="my" exception="conm.cad.struts2.MyException"></exception-mapping>
                <result name="my">/exception.jsp</result>
                <result name="success">/welcome.jsp</result>
                <result name="error">/error.jsp</result>    
        </action>
    </package>  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

struts2的未知处理器

从struts2.1开始,struts2增加了未知处理器。  

当用户请求未知Action,或指定Action里的未知方法,或Action处理结束后返回一个未知的result。struts2允许使用未知处理器来处理这些情况。

未知处理器需要实现UnknownHandler接口,该接口包含三个方法 

-handleUnknownAction:用户请求未知Action时,该方法会被回调
-handleUnknownActionMethod:用户请求指定Action的未知方法时,该方法会被回调
-handleUnknownResult:Action处理结束后返回一个未知Result时,该方法会被回调 

一旦实现了自定义的未知处理器,就可以在struts.xml中通过<bean../>元素来配置未知处理器
例如 
        <bean name="myHandler" class="com.cad.struts2.MyUnknownHandler" type="com.opensymphony.xwork2.UnknownHandler"></bean> 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

未知处理器案例

        第一步:我们实现我们简单的未知处理器

            public class MyUnknownHandler implements UnknownHandler {

                public ActionConfig handleUnknownAction(String namespace, String actionName) throws XWorkException {
                    System.out.println(actionName+"不存在");
                    return null;
                }


                public Result handleUnknownResult(ActionContext actionContext, String actionName, ActionConfig actionConfig,
                        String resultCode) throws XWorkException {
                    System.out.println(resultCode+"不存在");
                    return null;
                }


                public Object handleUnknownActionMethod(Object action, String methodName) throws NoSuchMethodException {
                    System.out.println(methodName+"不存在");
                    return null;
                }

            } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
            第二步:在struts.xml中配置未知处理器 

            //定义我们的未知处理器
            <bean name="myHandler" class="com.cad.struts2.MyUnknownHandler" type="com.opensymphony.xwork2.UnknownHandler"></bean>

            <package name="p3" extends="struts-default">
                <action name="myAction"></action>
            </package> 
            //定义未知处理器栈
            <unknown-handler-stack>
                <unknown-handler-ref name="myHandler"></unknown-handler-ref>
            </unknown-handler-stack> 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
然后我们访问myAction,会输出success不存在。
是因为不指定action的class属性时,默认使用ActioSupport
一般未知处理器主要处理Action返回未知result时有用。
了解即可。 
  • 1
  • 2
  • 3
  • 4
  • 5

struts2的类型转换

所有的MVC框架,都属于表现层的解决方案,都需要负责收集用户请求参数,并将请求参数传给应用的控制器组件。

这时问题出现了,所有的请求参数都是字符串类型数据,因此MVC框架必须具备将这些字符串请求参数转换成相应的数据类型。

struts2提供了非常强大的类型转换机制,struts2的类型转换是基于OGNL表达式

struts2提供了很好的扩展性,开发者可以开发出自己的类型转换器。完成字符串到自定义类型之间的转换。

如果类型转换中出现未知异常,开发者无须关心异常处理,struts2的conversionError拦截器会自动处理该异常,并且在页面上生成提示信息。

servlet中的类型转换

表单中提交的所有数据都是字符串类型

例如我们有一个User类,name为String类型,age为int类型,birthday为Date类型,我们必须在servlet中获取表单传入的参数,然后将其进行类型转换,然后封装到User对象中。

上述需要程序员自己进行类型转换操作,比较繁琐。

对于一个MVC框架而言,一样需要将请求参数封装成对象,也必须将请求参数转换成对象属性的数据类型,这就是类型转换的意义。

Struts2内建的类型转换器

struts内建的类型转换器能自动将我们的表单数据(字符串)转换成对应的数据类型。

完成字符串和日期类型之间的转换时,日期格式必须使用请求用户本地的格式。一般是yyyy-MM-dd,如果输入的日期格式不是本地的日期格式,例如我们输入1996/01/31,就会出现错误,类型转换失败。

自定义类型转换器

需求:

当我们在表单中输入的日期格式不是本地的格式时,就会出现类型转换错误,我们也经常需要将字符串转换成其他的格式,例如字符串转换成对象之类的操作,这时我们就需要自定义类型转换器。 

    struts2的类型转换器实际上是基于OGNL实现的。xwork集成了OGNL。 

    实现类型转换器必须实现TypeConverter接口。这个接口的方法太过复杂,所以还提供了一个该接口的实现类DefaultTypeConverter。 

    我们重写DefaultTypeConverter类的convertValue方法即可。 

    我们基于DefaultTypeConverter类实现类型转换器时,将字符串转换成我们需要的类型通过convertValue方法实现,将我们的类型转换成字符串也是通过convertValue方法实现,因此我们必须判断转换的类型来实现不同的逻辑。 

    为了简化类型转换器的实现,struts2提供了一个StrutsTypeConverter抽象类,这个类是DefaultTypeConverter类的子类。

    我们看下这个类的源码 :
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
    继承DefaultTypeConverter
public abstract class StrutsTypeConverter extends DefaultTypeConverter { 

        //重写DefaultTypeConverter类的convertValue方法
        public Object convertValue(Map context, Object o, Class toClass) { 
            //如果要转换的类型是字符串类型,也就是把我们的类型转换成字符串,调用convertToString方法
            if (toClass.equals(String.class)) {
                return convertToString(context, o);
            }
            //如果参数是字符串数组,也就是将字符串转换成我们需要的类型,调用convertFromString方法        
            else if (o instanceof String[]) {
                return convertFromString(context, (String[]) o, toClass);
            } 
            //如果参数是字符串,也就是将字符串转换成我们需要的类型,调用convertFromString方法
            else if (o instanceof String) {
                return convertFromString(context, new String[]{(String) o}, toClass);
            } else {
                return performFallbackConversion(context, o, toClass);
            }
        }


        protected Object performFallbackConversion(Map context, Object o, Class toClass) {
            return super.convertValue(context, o, toClass);
        }

        //将字符串转换成我们需要的类型的方法
        public abstract Object convertFromString(Map context, String[] values, Class toClass);

        //将我们的类型转换成字符串的方法
        public abstract String convertToString(Map context, Object o);
    } 

            三个参数 :
                    Map context:OGNL的上下文。暂时还没学,后面会学到,暂时不用管。
                    value:需要转换的参数。
                    toClass:转换后的类型
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

自定义类型转换器案例

        需求:我们将我们前面的注册案例中的生日改成  yyyy/MM/dd类型  

        第一步:创建自定义类型转换器 

        public class MyConverter extends StrutsTypeConverter { 
                    //日期转换器,转换成指定的类型
                    private DateFormat format=new SimpleDateFormat("yyyy/MM/dd");

                    //将字符串转换成日期类型
                    public Object convertFromString(Map context, String[] values, Class toClass) { 
                        //判断参数是否为空
                        if(values==null||values.length==0){
                            return null;
                        } 
                        //我们只有一个参数,就是表单的birthday
                        String date=values[0]; 
                        //判断目标类型是否是Date
                        if(toClass==java.util.Date.class){
                            try { 
                                //进行转换
                                return format.parse(date);
                            } catch (ParseException e) {
                                e.printStackTrace();
                            }
                        }
                        return null;    
                    }


                    //将日期类型转换成字符串
                    public String convertToString(Map context, Object o) { 
                        //判断当前参数是否是日期类型
                        if(o instanceof java.util.Date){
                            return format.format(o);
                        }
                        return null;
                    }

                }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
第二步:注册类型转换器

局部类型转换器
        按照属性来注册
        如果属性都在action中,那么应该创建一个文件 Action名字-conversion.properties ,例如LoginAction-conversion.properties 
        如果属性放到了javaBean中,那么创建一个文件 javaBean名称-conversion.properties 例如  User-conversion.properties

        文件由键值对组成。
        键为需要转换的属性名字,值为自己实现的类型转换器全类名。

        我们创建 User-conversion.properties 

        内容 birthday=com.cad.web.convert.MyConverter 

        这时我们注册时使用 1996/01/24这种格式进行注册就不会出现类型转换错误。
        用户提交请求时,请求中的birthday参数会先被该类型转换器处理。

全局类型转换器
        所有的Action都能用。我们需要在src目录下创建一个 xwork-conversion.properties 的文件

        因为是全局的,就不存在只对birthday这个属性进行转换。
        这里的键是要转换的类型,值还是类型转换器类。

        我们创建 xwork-conversion.properties 
        内容 java.util.Date=com.cad.web.convert.MyConverter 
        这样当我们输入日期的表单时,就可以使用我们自定义的日期格式。 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

类型转换中的错误处理

struts2提供了一个名为conversionError的拦截器,这个拦截器被注册在默认拦截器栈中。

当类型转换器执行类型转换出现错误时,该拦截器负责将对应错误封装成表单域错误(fieldError),并将错误信息放入ActionContext中。

当拦截器对转换异常进行处理后,系统会跳转到名为input的逻辑视图。

            我们在struts.xml中配置 <result name="input">/regist.jsp</result> 
            当类型转换失败后,再跳转到注册页面

            跳转到input视图以后,我们发现没有任何错误提示信息。我们前面讲过conversionError拦截器会将转换错误封装成fieldError,并放在ActionContext中。
            为了在页面中输出错误信息,我们需要使用struts的标签。我们先使用一些,后面会详细介绍。

            我们在页面添加<s:fielderror></s:fielderror>标签
            当我们类型转换失败后,就会输出错误信息。

            我们发现输出的错误信息是英文的,我们希望能变为中文的提示信息。

            我们只需要在创建一个properties文件  文件名为  javabean名称.properties  

            键为invalid.fieldvalue.属性名称  例如 :invalid.fieldvalue.birthday 
            值为要输出的内容 例如 invalid.fieldvalue.birthday=生日格式不正确  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

struts2的输入校验

输入校验是web应用必须处理的问题,要防止用户的误输入和恶意非法输入。struts2给我们提供了非常强大的输入校验体系。 

输入校验分为客户端校验和服务器端校验。一般开发中两者都用,但是服务端校验必须使用。 

客户端校验是通过javascript在表单页面进行初步过滤。客户端校验并不安全,攻击者有很多方法可以绕过客户端校验,所以服务端校验是必不可少的。 

但是客户端校验必不可少,因为大多数浏览者都是正常用户,可以阻止一些误操作,降低了服务器的负载。 

服务端校验:
            我们以前在servlet中增加校验。是通过获取参数,然后判断参数是否为空和长度等等来进行校验。
            在servlet中使用硬编码进行输入校验乏味而又繁琐,struts2提供了基于校验框架的输入校验,只需要指定简单的配置文件即可。 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

声明式验证:通过xml配置文件进行验证

-校验规则文件与Action类放在同一目录下 
-校验配置文件名称为 ActionClassName-validation.xml  例如 :UserAction-validation.xml 

增加校验文件后,系统会自动加载该文件。当用户提交请求时,struts2会根据该文件对用户数据进行校验 
  • 1
  • 2
  • 3
  • 4
  • 5

基于表单字段的配置风格

<?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE validators PUBLIC
            "-//Apache Struts//XWork Validator 1.0.3//EN"
            "http://struts.apache.org/dtds/xwork-validator-1.0.3.dtd"> 
    //校验文件的根元素      
    <validators>
        //被校验表单项名称
        <field name="username"> 
            //指定校验器,struts2有很多内建校验器,requiredstring是校验数据是否为空并去除两端空格
            <field-validator type="requiredstring"> 
                //校验失败后的提示信息
                <message>用户名不能为空</message>
            </field-validator>
        </field>
    </validators>   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
校验失败后,struts2会返回名为input的逻辑视图名,因此我们还需要添加一个<result>   

这个配置文件是全局验证。也就是这个Action中的所有动作都会被验证,我们Action有的方法并不需要验证,加入验证甚至会出错。 

    我们有两种方式可以让不需要验证的方法跳过验证: 

    第一种:在不需要验证的方法前面添加注解@SkipValidation  

    第二种:针对动作类中的某个方法进行验证,创建的XML文件名为 ActionClassName-ActionName-validation.xml ,这里的ActionName不是方法名,而是配置的action名字 ,例如 :UserAction-regist-validation.xml 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

非字段配置风格(基于验证器的配置风格)

<validators> 
                //验证器类型
                <validator type="requiredstring">
                    //要校验的参数名字
                    <param name="fieldName">password</param>
                    //校验失败后返回的信息
                    <message>密码不能为空</message>
                </validator>
            </validators>   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

短路校验器

校验配置文件的<field-validator>和<validator>元素可以指定一个可选的short-circuit属性,指定该校验器是否是短路校验器,默认是false。 

    短路校验器的作用是如果一个字段内有多个校验器,如果一个校验器校验失败,其他校验器根本不会继续校验。

    校验器的执行顺序
        -所有基于验证器的配置风格的校验器优先于字段风格的校验器
        -所有的基于验证器风格的校验器,排在前面的先执行 
        -所有的基于字段风格的校验器,排在前面的先执行。 

    校验器的短路原则
        -所有的非字段校验器最先执行,如果某个非字段校验器校验失败,则该字段上的所有字段校验器都不会执行
        -非字段校验器校验失败,不会阻止其他非字段校验器的执行
        -如果某个字段校验器校验失败,则该字段下的后面的字段校验器都不会执行
        -字段校验器永远都不会阻止非字段校验器的执行 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

struts2的内建校验器

            required:必填验证器,要求指定的字段必须有值。使用非字段风格的配置时,可以配置fieldName属性来设置要校验的表单项名称。 

            requiredstring:必填字符串验证器。 

            int、long、short:整数校验器。要求字段的整数值必须在指定范围内。参数:min指定该属性最小值,不指定不检查最小值。max指定该属性最大值,不指定不检查最大值。 

            date:日期校验器。要求字段的日期值必须在指定范围内。参数:min最小日期 ,max最大日期 

            expression:表达式校验器,它只能被非字段风格配置。参数:expression指定一个逻辑表达式。 

            fieldexpression:字段表达式校验器。要求字段满足一个逻辑表达式。 

            email:邮件校验器。要求被检查字段非空,并且必须是合法的邮箱地址,底层是正则表达式。 

            url:网址校验器。要求被检查字段非空并且是个发的url地址,底层是正则表达式。 

            stringlength:字符串长度校验器。要求字段长度必须在指定的范围内。参数:manLength 最大长度  minLength最小长度 

            regex:正则表达式校验器。
            等等。 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

自定义校验。

struts2内建的校验器可以完成大部分输入校验。但是有时候无法满足一些特殊的要求,struts2允许通过手动方式完成自定义校验。 


        继承ActionSupport,重写validate方法
            public void validate() {
                if(user.getUsername()==null||user.getUsername().isEmpty()){
                    addFieldError("username", "用户名不能为空!!!");
                }
            } 

        重写validate方法会检验action类里的所有方法,我们不需要校验某些方法,有两种方法。 

        第一种:在不需要校验的方法前加上注解@SkipValidation 
        第二种:重写validateXxx方法,Xxx即为要验证的方法名 
            public void validateRegist() {
                if(user.getUsername()==null||user.getUsername().isEmpty()){
                    addFieldError("username", "用户名不能为空!!!");
                }

            } 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

struts2的输入校验流程

1.对字符串类型的请求参数进行类型转换,并且设置给JavaBean的属性 

2.类型转换中可能出现异常,如果出现异常,将异常信息保存并封装 

3.调用struts2的输入校验规则进行输入校验(根据各种vatidate.xml文件里定义的校验规则) 

4.通过反射调用validateXxx方法进行校验  

5.调用validate方法校验 

6.上面的校验出现错误,转到input对应的视图资源。没错误,调用action中的处理方法。

猜你喜欢

转载自my.oschina.net/architectliuyuanyuan/blog/1800365