• struts2进阶


    Struts2

    一、Struts的工作原理

    Struts2的工作机制3.1Struts2体系结构图

      Strut2的体系结构如图15所示:

      (图15)

      3.2Struts2的工作机制

      从图15可以看出,一个请求在Struts2框架中的处理大概分为以下几个步骤:

      1、客户端初始化一个指向Servlet容器(例如Tomcat)的请求;

      2、这个请求经过一系列的过滤器(Filter)(这些过滤器中有一个叫做ActionContextCleanUp的可选过滤器,这个过滤器对于Struts2和其他框架的集成很有帮助,例如:SiteMesh Plugin);

      3、接着FilterDispatcher被调用,FilterDispatcher询问ActionMapper来决定这个请求是否需要调用某个Action;

      4、如果ActionMapper决定需要调用某个Action,FilterDispatcher把请求的处理交给ActionProxy;

      5、ActionProxy通过Configuration Manager询问框架的配置文件,找到需要调用的Action类;

      6、ActionProxy创建一个ActionInvocation的实例。

      7、ActionInvocation实例使用命名模式来调用,在调用Action的过程前后,涉及到相关拦截器(Intercepter)的调用。

    8、一旦Action执行完毕,ActionInvocation负责根据struts.xml中的配置找到对应的返回结果。返回结果通常是(但不总是,也可能是另外的一个Action链)一个需要被表示的JSP或者FreeMarker的模版。在表示的过程中可以使用Struts2 框架中继承的标签。在这个过程中需要涉及到ActionMapper。

    二.Action封装请求参数

    1 Action如何获取请求参数

    我们已经学习过使用ActionContext#getParameters()方法获取请求参数!但这种方式需要自己来获取,很麻烦。

    我们还学习过在Actoin获取Servlet API,所以我们可以先获取request对象,然后再通过request对象来获取请求参数,例如:ServletActionContext.getRequest().getParameter(“xxx”)。

    现在我们要学习新的方式,使用属性驱动,或模型驱动。

    如果现在有一个login.jsp页面,它提交到LoginAction!那么在LoginAction中给出username和password属性,那么这就是属性驱动;如果在LoginAction中给出User类型的属性,那么就是模型驱动了。

    public class LoginAction extends   ActionSupport {//属性驱动

    private   String username;

    private   String password;

    ….

    }

    public class LoginAction extends   ActionSupport {//模型驱动

    private User   user;

    ….

    }

    2 属性驱动

    属性驱动方式来封装请求参数,这需要在Action中给出属性,用来封装表单属性。并且属性名称需要与表单项名称相同!其实这是JavaBean规范,只要Action有setXXX()方法即可

     

    回忆Struts2流程流,在Action执行之前会先去执行一系列的拦截器,拦截器与JavaWeb中的过滤器及其相似!你现在甚至可以把拦截器理解成过滤器。在系列拦截器中有一个命名为params的拦截器会负责把请求参数封装到Action的属性中。

    我们知道,拦截器是在Action之前执行的,也就是说,在执行UserAction的login()方法之前,Struts2已经把表单数据封装到UserAction的username和password属性中了。所以在login()方法中可以大胆的使用这些属性。

    并且不会出现UserAction有属性会出现多线程争用数据的问题!因为在Struts2中Action都不是单例的!struts2 Action 是多实例 ,为了在Action封装数据  (struts1 Action 是单例的 )

    这种方式我们只需要做:让Action的属性名与表单项名称对应,那么拦截器会自动把请求参数装载到Action属性中。

    3 模型驱动之OGNL

    这里所说的模型驱动是在Action中给出JavaBean属性,而不是分散的username和password,而是使用User类型做为属性。

    public class User {

        private String username;

        private String password;

    ……

    }

    public class UserAction extends ActionSupport {

        private User user;

        public void setUser(User user) {

           this.user = user;

        }

        public User getUser() {

           return user;

        }

        public String login() {

           System.out.println(user);

           return NONE;

        }

    }

      这种方式会使Action属性名称不再与表单项名称相同,但我们发现与表单项名称相同的是Action的User属性的属性。我们如果可以让拦截器知道,去Action的User属性中去寻找属性。这需要使用OGNL表达式:

    <form action="<c:url value='/loginAction.action'/>" method="post">

        用户名:<input type="text" name="user.username"/><br/>

        密 码:<input type="password" name="user.password"/><br/>

        <input type="submit"   value="登录"/>

    </form>

      在表单中我们给出的表单项名称是user.username和user.password,这就是OGNL表达式,它表达的意思是,去找Action的名字为user的属性,再到user中去找username属性,这才是装载表单参数的地方。

    上面例子中需要注意:

    l  创建User类,User类需要提供setUsername()和setPassword()方法,与表单对应;

    l  UserAction中创建User类型的属性,属性名必须为user,并提供setUser()和getUser()方法;

    l  页面中表单项的名称必须使用OGNL表达式,例如user.username表示把该项值赋给UserAction的user属性的username属性。Struts2在首次赋值时会先创建User对象,然后再给User的username属性赋值,最后调用setUser()方法把User对象传递给UserAction;

    l  当再次通过OGNL表达user.password赋值时,这不是第一次赋值,所以Struts2会先去调用UserAction的getUser()方法获取User对象(因为第一次已经创建了,从此就不在创建),这时如果UserAction没有提供getUser()方法,就会出现第二次赋值失败的情况。如果存在getUser()方法,那么在获取到User对象后,再通过setPassword()给User对象指定属性值。

    4 模型驱动之ModelDriven接口

    这种方式与OGNL方式很相似,但表单页面中还是使用属性驱动的方式,不使用OGNL表达式,表单如下:

    <form action="<c:url value='/loginAction.action'/>" method="post">

        用户名:<input type="text" name="username"/><br/>

        密 码:<input type="password" name="password"/><br/>

        <input type="submit"   value="登录"/>

    </form>

    这时,Struts2还会去调用UserAction的setUsername()和setPassword()方法,但我们知道UserAction没有这两个方法,因为UserAction的属性是user。我们必须让Sturts2知道,我们使用的是模型驱动,这时就要求UserAction实现ModelDriven接口。

    public interface ModelDriven<T> {

        T getModel();

    }

       ModelDriven接口只有一个方法:getModel(),它用来返回model,在本例中应该返回的就是User对象。

    public class UserAction extends ActionSupport implements   ModelDriven<User> {

        private User user = new User();

        @Override

        public User getModel() {

           return user;

        }

        public String login() {

           System.out.println(user);

           return NONE;

        }

    }

      这种方式是真正的模型驱动,使用模型驱动方式完成参数封装是由modelDriven拦截器来完成的。它会检查UserAction是否实现了ModelDriven接口,如果实现了,那么就调用它的getModel()方法得到User对象,然后再去调用User对象的setUsername()和setPassword()来封装表单数据。注意,返回的user对象必须手动初始化,如果getModel()方法返回的是null,那么就无法封装表单数据。

    如果UserAction没有实现ModelDriven接口,那么Struts还会去查询UserAction是否存在setUsername()和setPassword()方法,即以属性驱动方式来封装表单数据,但因为UserAction没有这两个方法,那么就会失败。

    5 集合参数(了解)

    其实这种方式还是OGNL方式!

    当UserAction的属性为List<User>类型时,即不是一个User,而是一个集合时,那么在表单中需要使用“特殊”的OGNL表达式。

    public class UserAction extends ActionSupport {

        private List<User> userList;

        public List<User>   getUserList() {

           return userList;

        }

        public void setUserList(List<User>   userList) {

           this.userList = userList;

        }

        public String login() {

           System.out.println(userList);

           return NONE;

        }

    }

    <form action="<c:url value='/loginAction.action'/>" method="post">

        用户名0:<input type="text"   name="userList[0].username"/><br/>

        用户名1:<input type="text"   name="userList[1].username"/><br/>

        密 码0:<input type="password"   name="userList[0].password"/><br/>

        密 码1:<input type="password"   name="userList[1].password"/><br/>

        <input type="submit"   value="登录"/>

    </form>

    通过上面的方式也可以把表单数据封装到Map类型的属性中。

    <form action="<c:url value='/loginAction.action'/>" method="post">

        用户名0:<input type="text"   name="userMap[‘zhangSan’].username"/><br/>

        用户名1:<input type="text"   name="userMap[‘liSi’].username"/><br/>

        密 码0:<input type="password"   name="userMap[‘zhangSan’].password"/><br/>

        密 码1:<input type="password"   name="userList[‘liSi’].password"/><br/>

        <input type="submit"   value="登录"/>

    </form>

     

    四.类型转换(了解)

    1 Struts2类型转换器

    我们现在已经知道,Struts2可以把数据封装到Action的属性中!但我们知道,请求参数都是字符串类型的,而Action的属性如果是int类型,发现也没有出错,这是因为Struts2有能力把String转换成int类型,这就是类型转换了。

    Struts2提供了很多内置的类型转换器,用来把String转换成需要的类型,这些类型转换器基本上就够用了,所以我们很少自定义类型转换器:

    l  int和Integer;

    l  long和Long;

    l  float和Float;

    l  double和Double;

    l  char和Character;

    l  boolean和Boolean;

    l  Date(格式为:yyyy-MM-dd)

    public class UserAction extends ActionSupport {

        public void setMyDate(Date date) {

           System.out.println(date);

        }

        public String login() {

           return NONE;

        }

    }

    <form action="<c:url value='/loginAction.action'/>" method="post">

      日期:<input type="text" name="myDate"/><br/>

           <input type="submit" value="提交"/>

    </form>

    2 自定义类型转换器

    我们还可以来自定义类型转换器!

    类型转换器需要提供把String[]转换成属性类型,还需要把属性类型再转换成String类型。当提交表单时,是把String[]类型转换成属性类型(参数封装),当在页面中显示属性时,是把属性类型转换成String类型(数据回显)。所以我们要知道类型转换器是要支持双向转换的。

    实现自定义类型转换器:

    l  实现com.opensymphony.xwork2.conversion.TypeConverter(不方便);

    l  继承com.opensymphony.xwork2.conversion.impl.DefaultTypeConverter类,重写convertValue()方法(不方便);

    l  继承org.apache.struts2.util.StrutsTypeConverter类,重写convertFromString()和convertToString()方法(OK)。

    DefaultTypeConverter类实现了TypeConverter接口,我们继承DefaultTypeConverter类是需要覆盖它的convertValue(Map context, Object value, Class toType)方法:

    l  Map<String, Object> context:OGNL上下文对象,通常用不上它;

    l  Object value:被转换的值;

    l  Class toType:要被转换成什么类型。

    因为转换是双向的,所以我们需要判断toType是什么类型。通过判断toType的类型就可以知道当前转换的“方向”:

    if(toType == String.class) {//数据回显

    } else {//请求参数封装

    }

      注意,当要做请求参数封装时,value的类型不是String,而是String[]类型。所以你需要获取String[]的第一个参数,即下标0的值进行转换。

    我们写一个PersonConverter,它可以把String转换成Person类型,也可以把Person转换成String类型。

    public class PersonAction extends ActionSupport {

        private Person person;

        public Person getPerson() {

           return person;

        }

        public void setPerson(Person person) {

           this.person = person;

        }

        public String execute() {

           System.out.println(person);

           return NONE;

        }

    }

    public class PersonConverter extends DefaultTypeConverter {

        public Object   convertValue(Map<String, Object> context, Object value,

               Class toType) {

           if(toType == String.class) {

               return value.toString();

           } else {

               String s = ((String[]) value)[0];

               String[] strs = s.split(",");

               return new Person(strs[0], Integer.parseInt(strs[1]),   strs[2]);

           }

        }

    }

        <package name="s5"   namespace="/" extends="struts-default">

        <action name="PersonAction" class="cn.itcast.s5.aciton.PersonAction">

            <result>/index.jsp</result>

        </action>

        </package>

      <form action="<c:url value='/PersonAction.action'/>"   method="post">

        Person:   <input type="text"   name="person"/>

        <input type="submit" value="提交"/>

      </form>

    在继承DefaultTypeConverter类时,我们需要自己去判断toType的类型,然后再做双向的转换,这很不方便,所以Struts2又提供了org.apache.struts2.util.StrutsTypeConverter类,它是DefaultTypeConverter的子类,该类提供了两个抽象方法(convertFromString、convertToString),分别用来参数封装和数据回显!

    public class PersonConverter extends StrutsTypeConverter {

        // 参数封装,String --> Peson

        public Object convertFromString(Map   context, String[] values, Class toClass) {

           String[] s = values[0].split(",");

           return new Person(s[0], Integer.parseInt(s[1]),   s[2]);

        }

        // 数据回显    Person --> String

        public String convertToString(Map   context, Object value) {

           return value.toString();

        }

    }

    注意,上面代码还不能运行,因为Struts2无法找到PersonConverter,我们需要注册PersonConverter,这样Struts2才能找到它。

    l  局部注册:只为PersonAction的person属性使用的类型转换器;

    l  全局注册:所有Person类型都可以使用的类型转换器。

    局部注册:在PersonAction所在包下(即cn.itcast.action)创建名为ActionName-conversion.properties(即PersonAction-conversion.properties)的文件。内容为属性名称=转换器类名(即person=cn.itcast.converter.PersonConverter)。

    局部注册只对PersonAction类的person属性使用转换器!!!

    全局注册:在src下创建名为xwork-conversion.properties的文件,内容为属性类名=转换器类名(即cn.itcast.Person=cn.itcast.converter.PersonConverter)。

    全局注册会对所有Person类型的Action属性使用转换器!!!

    3 类型转换的错误信息

    当类型转换失败时,会出现NoSuchMethodException异常。这是因为拦截器在无法转换请求参数为目标类型后,会尝试以String[]为种原始的请求参数类型来封装到Action中,但如果Action没有提供这个原始的参数类型的set方法,那么就会报错。例如我们给PersonAction的int age属性赋值为abc时,那么就会出现这个异常:

    这个异常是在说,没有找到setAge(String[])方法!因为拦截器无法将abc转换成int类型时,它试图用请求参数的原始类型(String[])来封装到PersonAction中,所以会去找setAge(String[])方法,但没有找到,所以抛出异常。

    当出现类型转换错误后,params拦截器会记录这个错误,然后再由conversionError拦截器把错误存放到fieldError(字段错误)中,然后跳转到input结果码,这说明我们需要在<action>中配置<result name=”input>,然后在input页面中使用Struts2的标签<s:fieldError>来获取!

        <package name="s5"   namespace="/" extends="struts-default">

        <action name="PersonAction" class="cn.itcast.aciton.PersonAction">

            <result name="input">/form.jsp</result>

            <result>/result.jsp</result>

        </action>

        </package>

    <%@ page language="java"   import="java.util.*" pageEncoding="UTF-8"%>

    <%@ taglib prefix="c"   uri="http://java.sun.com/jsp/jstl/core"   %>

    <%@ taglib prefix="s"   uri="/struts-tags" %>

     

    <!DOCTYPE HTML   PUBLIC "-//W3C//DTD   HTML 4.01 Transitional//EN">

    <html>

      <head>

      </head>

      <body>

        <s:fielderror/>

        <form action="<c:url value='/PersonAction.action'/>"   method="post">

        姓名:<input type="text"   name="name"/><br/>

        年龄:<input type="text"   name="age"/><br/>

        性别:<input type="text"   name="gender"/><br/>

        <input type="submit" value="提交"/>

        </form>

      </body>

    </html>

      我们发现Struts2打印的类型转换错误信息是英文的,这说明我们需要自定义错误信息。自定义错误信息需要在Action所在目录下创建ActionName.properties文件(与Action同名的properties文件),然后在该文件中给出:invalid.fieldvalue.属性名=错误信息,其中invalid.fieldvalue是固定的。例如:invalid.fieldvalue.person=无法将请求参数转换成Person类型!

    PersonAction.proeprties

    invalid.fieldvalue.person=无法把表单参数转换成Person类型

    4 类型转换的流程

    五.输入校验

    1 什么是输入校验

    在Action封装了请求参数后,还需要对其进行校验。例如name不能为空,age只能在18~60之间等等!输入校验是在类型转换成功之后,才可能执行的。

    校验分类:

    l  JavaScript客户端校验(改善用户体验);

    l  服务器端校验(保证安全性),即使用Struts2输入校验。

    2 Struts2输入校验分类

    Struts2输入校验分为两种:

    l  代码方式校验;

    l  配置校验:

    • XML配置校验(常用);
    • 注解配置校验。

    这里只介绍XML配置校验  其他校验请参考struts2附录

    3 XML配置方式校验(最主要)

    代码校验缺点:

    代码方式校验方式会出现很多冗余。在某个校验方法中出现了“非空”校验逻辑,在另一个校验方法中还需要出现“非空”校验逻辑。

    代码校验不方法维护。当需要修改它时就要修改原代码。

    使用XML配置方式是先把常用的校验规则写好,然后在XML配置中指定要使用的校验规则。当然Struts2已经帮我们写好了很多的校验规则。我们只需要指在XML文档中配置当前的请求处理方法需要哪些校验规则。

    3.1 XML配置方式校验要求

    要使用XML配置方式校验,你的Action类必须实现Validateable接口。ActionSupport类已经实现了Validateable接口,所以我们通常是直接继承ActionSupport类。

    为属性提供getXXX()和setXXX()方法!代码校验是在Action本类中来完成校验,这说明我们可以直接使用本类的private属性,但如果使用XML配置方式校验,这需要使用校验框架的代码来完成校验工作,那么校验框架需要调用Action的getXXX()方法来获取被校验的属性,所以一定要为被校验的属性提供getXXX()方法。

    3.2 创建校验文件

    1. 校验文件的命名必须为:ActionName-validation.xml。例如LoginAction的校验文件命名为:LoginAction-validation.xml。

    2. 校验文件的路径:必须与Action在同包下。

    3. 校验文件的DTD:在xwork-core-x.x.x.jar中找到xwork-validator-x.x.x.dtd,打开它,内部会有一段DTD,我们把它copy过来,放到我们的校验文件中。

    xwork-validator-1.0.3.dtd

    <?xml version="1.0" encoding="UTF-8"?>

     

    <!--

      XWork Validators DTD.

      Used the following DOCTYPE.

     

      <!DOCTYPE validators PUBLIC

            "-//Apache   Struts//XWork Validator 1.0.3//EN"

            "http://struts.apache.org/dtds/xwork-validator-1.0.3.dtd">

    -->

    <!ELEMENT validators (field|validator)+>

    <!ELEMENT field (field-validator+)>

    <!ATTLIST field

        name CDATA #REQUIRED

    <!ELEMENT field-validator (param*, message)>

    <!ATTLIST field-validator

        type CDATA #REQUIRED

        short-circuit (true|false) "false"

    <!ELEMENT validator (param*, message)>

    <!ATTLIST validator

        type CDATA #REQUIRED

        short-circuit (true|false) "false"

    <!ELEMENT param (#PCDATA)>

    <!ATTLIST param

        name CDATA   #REQUIRED

    <!ELEMENT message (#PCDATA|param)*>

    <!ATTLIST message

        key CDATA   #IMPLIED

    4. 为了在MyEclipse中对XML有提供功能,那么还需要让MyEclipse导入DTD文件的位置。

    3.3 编写校验文件

    校验文件的元素结果如下:

    <validators>

        <field name="">

           <field-validator type="">

               <param name=""></param>

               <message></message>

           </field-validator>

        </field>

    </validators>

    <field>的name属性指定要校验的属性,例如<feld name=”username”>,表示要校验的属性是username属性。

    <field-validator>的type属性指定校验规则,校验规则由Struts2提供,Struts2提供的所有校验规则在:

     

    打印default.xml文件,内部如下:

    defualt.xml

    <?xml version="1.0"   encoding="UTF-8"?>

    <!DOCTYPE validators PUBLIC

              "-//Apache Struts//XWork Validator   Definition 1.0//EN"

              "http://struts.apache.org/dtds/xwork-validator-definition-1.0.dtd">

     

    <!-- START SNIPPET:   validators-default -->

    <validators>

        <validator name="required"   class="com.opensymphony.xwork2.validator.validators.RequiredFieldValidator"/>

        <validator name="requiredstring"   class="com.opensymphony.xwork2.validator.validators.RequiredStringValidator"/>

        <validator name="int" class="com.opensymphony.xwork2.validator.validators.IntRangeFieldValidator"/>

        <validator name="long" class="com.opensymphony.xwork2.validator.validators.LongRangeFieldValidator"/>

        <validator name="short" class="com.opensymphony.xwork2.validator.validators.ShortRangeFieldValidator"/>

        <validator name="double"   class="com.opensymphony.xwork2.validator.validators.DoubleRangeFieldValidator"/>

        <validator name="date" class="com.opensymphony.xwork2.validator.validators.DateRangeFieldValidator"/>

        <validator name="expression"   class="com.opensymphony.xwork2.validator.validators.ExpressionValidator"/>

        <validator name="fieldexpression"   class="com.opensymphony.xwork2.validator.validators.FieldExpressionValidator"/>

        <validator name="email" class="com.opensymphony.xwork2.validator.validators.EmailValidator"/>

        <validator name="url" class="com.opensymphony.xwork2.validator.validators.URLValidator"/>

        <validator name="visitor"   class="com.opensymphony.xwork2.validator.validators.VisitorFieldValidator"/>

        <validator name="conversion"   class="com.opensymphony.xwork2.validator.validators.ConversionErrorFieldValidator"/>

        <validator name="stringlength"   class="com.opensymphony.xwork2.validator.validators.StringLengthFieldValidator"/>

        <validator name="regex" class="com.opensymphony.xwork2.validator.validators.RegexFieldValidator"/>

        <validator name="conditionalvisitor"   class="com.opensymphony.xwork2.validator.validators.ConditionalVisitorFieldValidator"/>

    </validators>

    上面文件中每个<validator>元素都是一个校验规则,校验规则对应一些已经写好的方法,他们有校验属性非空的规则,有校验字符串属性长度的规则,有校验int属性范围的规则等等。通常我们不需要自己来编写校验规范,因为上面的校验规则已经足够了。

    每个规则都有自己的名字,校验文件中<field-validator>的type就是用来指定校验规则的名称。例如下面的代码是对username属性的非空校验:

    <validators>

        <field name="username">

           <field-validator type="requiredString">

            ……

           </field-validator>

        </field>

    </validators>

    其中type=”requiredString”是校验规则的名称,它必须对应defualt.xml文件中<validator>元素的name属性值。requiredString校验规则是校验字符串属性是否长度为0,如果长度为0,它会向fieldError中添加错误信息。

    <message>元素指定的是错误信息,例如:

    <validators>

        <field name="username">

           <field-validator type="requiredstring">

               <message>用户名不能为空</message>

           </field-validator>

        </field>

    </validators>

    每个校验规则还都有自己的参数,如果想知道每个校验规则有什么参数,那么最好的方法是去查看校验规则的源代码。

    例如requiredstring校验规则有一个trim参数,它是boolean类型的参数,当trim为true时,requiredString校验器会先调用属性的trim()方法(去掉前后空白),然后再校验长度是否为0。trim参数的默认值就是true。

    public class RequiredStringValidator extends FieldValidatorSupport {

        private boolean doTrim = true;

        public void setTrim(boolean trim) {

            doTrim = trim;

        }

        public boolean getTrim() {

            return doTrim;

        }

        public void validate(Object object) throws   ValidationException {

            String fieldName = getFieldName();

            Object value = this.getFieldValue(fieldName,   object);

            if (!(value instanceof String)) {

                addFieldError(fieldName, object);

            } else {

                String s = (String) value;

                if (doTrim) {

                    s = s.trim();

                }

                if (s.length() == 0) {

                    addFieldError(fieldName,   object);

                }

            }

        }

    }

      下面是完整的LoginActoin的校验文件:

    LoginAction-validation.xml

    <validators>

        <field name="username">

           <field-validator type="requiredstring">

               <!-- 调用校验器的setTrim()方法传递参数false -->

               <param name="trim">false</param>

               <message>用户名不能为空</message>

           </field-validator>

           <field-validator type="stringlength">

               <!-- 调用校验器的setMaxLength()方法传递参数16 -->

               <param name="maxLength">16</param>

               <!-- 调用校验器的setMinLength()方法传递参数3 -->

               <param name="minLength">3</param>

               <message>用户名长度必须在3~16之间</message>

           </field-validator>

        </field>

        <field name="password">

           <field-validator type="requiredstring">

               <param name="trim">false</param>

               <message>密码不能为空</message>

           </field-validator>

        </field>

    </validators>

    3.4 校验规则介绍

    l  required:当属性为null时校验失败;

    l  requiredstring:当字符串属性为null或长度为0时校验失败:

    • 参数trim:默认值为true,表示去除前后空白后再校验长度。

    l  stringlength:当字符串长度不在指定范围内时校验失败:

    • minLength:指定字符串的最小长度;
    • maxLength:指定字符串的最大长度。

    l  regex:属性不能匹配正则表达式时校验失败:

    • expression:指定正则表达式;
    • caseSensitive:默认值为true,表示不忽略大小写。

    l  int:当int属性不在指定范围内校验失败:

    • min:最小值;
    • max:最大值。

    l  double:当double属性不在指定范围内校验失败:

    • min:最小值;
    • max:最大值。

    l  fieldexpression:属性必须是OGNL表达式:

    • expression:用来校验的ONGL表达式,例如pass == repass,其中pass和repass是两个属性名称,当这两个属性的值相等时校验通过,否则失败。

    l  email:属性必须是合法的邮件地址;

    l  url:属性必须是合法的网址;

    l  date:属性必须是合法的日期格式。

    六.拦截器

    1 理解拦截器

    来自AOP(面向切面)思想。它看起来与JavaWeb中的Filter极其相似。我们已经知道请求会先过一系列拦截器,最终到达Action,或者中途中断。也就是说,每个拦截器都有中断请求的能力。

    因为现在还不是讲AOP的时候,所以我们现在只要把拦截器理解为JavaWeb中Filter即可。你回忆一下Filter与Servlet的关系,那么Interceptor与Action就是相同的关系。

    2 Struts2中的拦截器

      在Struts2中定义了很多拦截器,你可以去struts-default.xml文件中查看。

    自定义拦截器

    注意:当拦截器拦截,返回结果集后,则后面的都不会执行,直接返回结果集。

    我们也可以自定义拦截器,Struts2要求所有拦截器必须实现Interceptor接口。

    Interceptor.java

    public interface Interceptor extends Serializable {

        void destroy();

        void init();

        String intercept(ActionInvocation   invocation) throws Exception;

    }

    Struts2还提供了一个Interceptor接口的实现类:AbstractInterceptor,通常我们自定义拦截器都是通过继承AbstractInterceptor类,而不是实现Interceptor接口。

    AbstractInterceptor.java

    public abstract class AbstractInterceptor implements Interceptor {

        public void init() {}

        public void destroy() {}

        public abstract String   intercept(ActionInvocation invocation) throws Exception;

    }

    继承AbstractInterceptor类时,不需要“被迫”实现init()和destroy()方法,而只需要关注intercept()方法即可。

     

    1、执行效率统计拦截器

    建立拦截器ElapsedTimeInterceprot实现Interceptor接口

    //动作方法及结果处理耗时统计拦截器

    public class ElapsedTimeInterceprot implements Interceptor {

        public String intercept(ActionInvocation invocation) throws Exception {

            long beginTime = System.nanoTime();//纳秒:1毫秒=1000000纳秒

            String result = invocation.invoke();//放行:拦截前要做的事放在invocation.invoke()之前,拦截后放在之后

            //结果处理完毕后执行

            long endTime = System.nanoTime();

            System.out.println(invocation.getInvocationContext().getName()+"动作执行耗时:"+(endTime-beginTime)+"纳秒");

            return result;

        }

        public void destroy() {

        }

        public void init() {

        }

    }

    进行配置文件设置

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

        <interceptors>

            <interceptor name="elapsedTime" class="com.itheima.interceptors.ElapsedTimeInterceprot"></interceptor>

        </interceptors>

        <action name="test1" class="com.itheima.action.HelloAction1" method="test1">

            <!-- 需要获取request,所以必须先执行servletConfig拦截器 -->

            <interceptor-ref name="defaultStack"></interceptor-ref>

            <!-- 执行耗时统计拦截器 -->

            <interceptor-ref name="elapsedTime"></interceptor-ref>

            <result>/1.jsp<result>

        </action>

    </package>

    拦截器的扩展,定义拦截器小组

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

        <interceptors>

            <interceptor name="elapsedTime" class="com.itheima.interceptors.ElapsedTimeInterceprot"></interceptor>

            <!-- 自己定义一个拦截器小组 -->

            <interceptor-stack name="myDefaultStack">

                <interceptor-ref name="defaultStack"></interceptor-ref>

                <interceptor-ref name="elapsedTime"></interceptor-ref>

            </interceptor-stack>

        </interceptors>

        <action name="test1" class="com.itheima.action.HelloAction1" method="test1">

            <!-- 执行自定义的拦截器小组 -->

            <interceptor-ref name="myDefaultStack"></interceptor-ref>

            <result>/1.jsp<result>

        </action>

    </package>

    继续扩展配置设置

    <package name="mypackage" extends="struts-default" abstract="true">

        <interceptors>

            <interceptor name="elapsedTime" class="com.itheima.interceptors.ElapsedTimeInterceprot"></interceptor>

            <interceptor name="sessionCheck" class="com.itheima.interceptors.SessionCheckInterceptors">

                <!-- 说明test2动作方法不需要拦截 -->

                <param name="excludeMethods">test2</param>

            </interceptor>

            <interceptor-stack name="myDefaultStack">

                <interceptor-ref name="defaultStack"></interceptor-ref>

                <interceptor-ref name="elapsedTime"></interceptor-ref>

                <interceptor-ref name="sessionCheck"></interceptor-ref>

            </interceptor-stack>

        </interceptors>

        <!-- 设置该包中的所有action配置默认拦截器 ,每个包只能指定一个默认拦截器 -->

        <default-interceptor-ref name="myDefaultStack"></default-interceptor-ref>

    </package>

     

    2、是否登陆拦截器MethodFilterInterceptor(可以配置是否进行拦截excludeMethods属性,如上)

    权限判断拦截器继承MethodFilterInterceptor类,这样只对某些方法起作用,而对其他方法不起作用。 (配置文件如上)

    //执行动作方法前检查用户是否已经登录

    public class SessionCheckInterceptors extends MethodFilterInterceptor{

        protected String doIntercept(ActionInvocation invocation) throws Exception {

            String result = "login";//对应的就是一个结果

            HttpSession session = ServletActionContext.getRequest().getSession();

            User user = (User)session.getAttribute("user");

            if(user!=null)

                //如果用户有登录,则放行。

                result = invocation.invoke();

            //如果用户没有登录,则返回结果集。

            return result;

        }

    }

     配置文件需要在结果集中增加一个

    <result name="login">/login.jsp</result>

     

     注册拦截器

    注册拦截器一共分为两步:

    l  在<package>中声明拦截器;

    l  在<action>中引用拦截器。

        <package name="s8"   namespace="/" extends="struts-default">

           <interceptors>

               <interceptor name="MyInterceptor" class="cn.itcast.interceptor.MyInterceptor"   />

           </interceptors>

           <action name="LoginAction">

               <result>/index.jsp</result>

               <result name="input">/login.jsp</result>

               <interceptor-ref name="MyInterceptor" />

           </action>

        </package>

    上面的代码虽然可以执行MyInterceptor了,但因为Struts2有这么一种机制,一旦为Action指定了拦截器,那么就不会再为这个Action执行默认拦截器了,即defaultStack这个拦截器栈中的拦截器都不会执行,也就是说,这个Action没有输入校验、没有参数注入、没有国际化、没有…,这是不行的,所以我们需要在这个<action>元素中再引用defaultStack拦截器栈。

        <package name="s8"   namespace="/" extends="struts-default">

           <interceptors>

               <interceptor name="MyInterceptor" class="cn.itcast.interceptor.MyInterceptor"   />

           </interceptors>

           <action name="LoginAction">

               <result>/index.jsp</result>

               <result name="input">/login.jsp</result>

               <interceptor-ref name="defaultStack" />

               <interceptor-ref name="MyInterceptor" />

           </action>

        </package>

    在<aciton>元素中引用拦截器的顺序决定了拦截器的执行顺序,上例中会先执行defaultStack中的所有拦截器,再执行MyInterceptor拦截器。

    上面的方式虽然可以注册拦截器,但比较麻烦。因为如果当前包中所有<action>都需要执行MyInterceptor拦截器,那么就需要在每个<action>元素中引入拦截器。其实还有另一种方式,就是为当前包指定默认拦截器栈!

    我们都知道,因为我们的包继承了struts-default包,所以默认的拦截器栈是defaultStack,但没有为<action>元素指定拦截器时,那么就会执行defaultStack拦截器栈。我们可以在<package>中声明一个拦截器栈,然后在去替换默认拦截器栈即可。

        <package name="s8"   namespace="/" extends="struts-default">

           <interceptors>

               <interceptor name="MyInterceptor" class="cn.itcast.interceptor.MyInterceptor"   />

               <interceptor-stack name="myStack">

                  <interceptor-ref name="defaultStack" />

                  <interceptor-ref name="MyInterceptor" />

               </interceptor-stack>

           </interceptors>

           <default-interceptor-ref name="myStack" />

           <action name="LoginAction">

               <result>/index.jsp</result>

               <result name="input">/login.jsp</result>

           </action>

        </package>

    八.上传

    1        上传下载组件介绍

    l  jspSmartUpload(model1的年代);

    l  apache-commons-fileupload,Struts2默认上传组件;

    l  Servlet3.0使用的Part,但Servlet3.0还没有普及;

    l  COS,Struts2支持,不过已经停止更新很久了;

    l  pell,Struts2支持。

    2        fileUpload的拦截器

    Struts2默认使用的是commons-fileUpload组件完成上传的,使用Struts2会大量简化上传文件的开发。这一工作由fileUpload拦截器来完成。它会查看当前请求的enctype是否为multipart/form-data,如果不是就会直接“放行”;如果是,那么它会去解析表单,然后把解析的结果传递给Action的属性!

      fileUpload拦截器对会对Action提供很大的“帮助”,同时它也会对Action提出一些“小小的要求”。Action需要提供3个属性:

    l  File fieldName

    l  String fieldNameContentType

    l  String fieldNameFileName;

    三个属性的前缀都(fieldName)必须与文件表单项名称一致,例如有文件表单项内容为:<input type=”file” name=”myUpload”/>,其中表单项名称为:myUpload,那么Action就必须要有如下3个属性:

    l  private File myUpload

    l  private String myUploadContentType

    l  private String myUploadFileName

    3 上传配置

    可以通过Struts2的常量来完成对上传的配置,下面是与上传相关的常量:

    l  struts.multipart.parser:指定使用的上传组件,默认值为jakarta,表示使用commons-fileupload组件,Struts2还支持cos和pell;

    l  struts.multipart.saveDir:临时目录,如果没有指定临时目录,那么临时文件会在Tomcat的work目录中;

    l  struts.multipart.maxSize:整个大小限制,默认值为2097152,即2M。注意,这个限制是整个请求的大小,而不是单一文件的大小。

    当上传的表单超出了限制时,拦截器会向actionError中添加错误信息!当执行wokflow拦截器时,会发现actionError中存在错误,这时就会跳转到input视图,所以我们需要为Action指定input视图。

    fileUpload拦截器也有3个参数,我们可以给fileUpload拦截器配置这3个参数:

    l  maximumSize:上传的单个文件的大小限制;

    l  allowedTypes:允许上传文件的类型,多个类型以逗号隔开;

    l  allowedExtensions:允许上传文件的扩展名,多个扩展名以逗号隔开;

    <struts>

        <constant name="struts.devMode"   value="true" />

        <constant name="struts.multipart.maxSize"   value="1048576" />

        <package name="s8"   namespace="/" extends="struts-default">

           <action name="UploadAction" class="cn.itcast.upload.action.UploadAction">

               <result name="input">/demo1/upload.jsp</result>

               <param name="savepath">/WEB-INF/uploads</param>

               <interceptor-ref name="defaultStack">

                  <!-- 限制单个文件大小上限为512K -->

                  <param name="fileUpload.maximumSize">524288</param>

                  <param name="fileUpload.allowedExtensions">jpg,png,bmp</param>

               </interceptor-ref>

           </action>

        </package>

    </struts>

    4 上传相关的错误信息国际化

    在上传文件时如果出现错误,那么在input视图显示的错误信息都是英文的。如果想替换这些信息,需要知道这些错误信息的资源key,然后在我们自己的国际化资源文件中指定这些key的新值即可。

    与上传相关的错误信息都在org.apache.struts2包下的struts-message.properties文件中。

    struts.messages.error.uploading=Error   uploading: {0}

    struts.messages.error.file.too.large=The   file is to large to be uploaded: {0} "{1}" "{2}" {3}

    struts.messages.error.content.type.not.allowed=Content-Type   not allowed: {0} "{1}" "{2}" {3}

    struts.messages.error.file.extension.not.allowed=File   extension not allowed: {0} "{1}" "{2}" {3}

    struts.messages.upload.error.SizeLimitExceededException=Request   exceeded allowed size limit! Max size allowed is: {0} but request was: {1}!

      我们可以在src下res.properties文件,在这个文件中对象以上资源key进行替换。然后在struts.xml文件中给出<constant name="struts.custom.i18n.resources" value="res/">即可。

    5 多文件上传

    当需要上传多个文件时,如果每个<input type=”file”>对应Action的3个属性,那么这会使Action的属性过多的现象。处理这一问题的方法是在表单中设置所有<input type=”file”>的名称为相同名称,然后在Action中给出数组属性即可。

    九.文件下载

    1 回忆JavaWeb文件下载

    其实也可以让用户通过浏览器直接访问下载资源,但这样程序无法控制下载流程了。所以在JavaWeb中通过Servlet完成下载。

    下载是与响应相关的操作,在JavaWeb中下载要做如下操作:

    l  设置Content-type响应头,其实这个头你不设置浏览器也会自己通过文件名来识别;

    l  设置Disposition响应头,它默认值是inline,表示在页面中打开,下载时应该设置为attachment,表示让浏览器弹出下载框;

    l  处理文件中乱码问题;

    l  获取被下载文件,把被下载文件的内容写入到response.getOutputStream()流中;

    2 Struts2结果类型之stream

    在Struts2中,所有与响应相关的工作都由<result>来处理。我们知道,<result>的默认类型为dispatcher,表示请求转发到JSP,如果设置<result>的type为redirect,那么表示重定向。也就是说所有和响应相关的事情都交给<result>元素指定的类来处理。

    Struts2专门提供了一个stream类型的Result,它用来完成下载。也就是说在下载时我们的Action中给出的<result>的type类型应该指定为stream。stream结果对应的结果类型为StreamResult类!这个类来完成设置响应头,以及向response.getOutputStream()中写入数据。

    StreamResult类对Content-type提供的默认值为text/plain,这不是我们想要的,所以我们需要给StreamResult设置新的Cotnent-type。

    StreamResult类对disposition头提供的默认值为inline,这也不是我们想要的,所以我们也需要给它设置为attachment。

    在StreamResult类中提供了两个属性:contentType和contentDisposition,我们需要给这两个属性赋值,来替换默认值。

    StreamResult类还要求Action提供一个getInputStream()方法,它会通过这个方法来获取一个InputStream对象,它会把这个流中的内容写入到response.getOutputStream()中。

    3 OGNL表达式获取Action属性值

      上例中要下载的文件是硬编码,文件的ContentType类型是硬编码,下载框中的文件名也是硬编码,这些都是需要处理的。

           <action name="Download1Action" class="cn.itcast.download.action.Download1Action">

               <param name="downloadDir">/WEB-INF/downloads</param>

               <result type="stream" name="success">

                  <param name="contentType">${contentType}</param>

                  <param name="contentDisposition">attachment;filename=${filename}</param>

               </result>

           </action>

    Download1Action

    public class Download1Action extends ActionSupport {

        private String downloadDir;

        private InputStream inputStream;

        private String filename;

       

        public void setDownloadDir(String   downloadDir) {

           this.downloadDir = ServletActionContext.getServletContext().getRealPath(downloadDir);

        }

     

        public void setFilename(String filename)   throws UnsupportedEncodingException {

           // 处理GET请求中文编码问题

           this.filename = new   String(filename.getBytes("iso-8859-1"), "utf-8");

        }

     

        public String getFilename() throws   UnsupportedEncodingException {

           // 在下载框中显示的文件名需要处理编码问题

           return new String(filename.getBytes("GBK"), "iso-8859-1");

        }

       

        public String getContentType() {

           // 文件的MIME类型

           return ServletActionContext.getServletContext().getMimeType(filename);

        }

     

        public InputStream getInputStream()   throws FileNotFoundException {

           return this.inputStream;

        }

     

        public String execute() throws Exception   {

           File file = new File(downloadDir, filename);

           inputStream = new   FileInputStream(file);

           return SUCCESS;

        }

    }

     

  • 相关阅读:
    AllowsTransparency和WebBrowser兼容性问题解决方案
    webbrowser和js交互小结
    wpf数据验证实例及常用方法小结
    Scrum 冲刺第一天
    Scrum 冲刺第二天
    1625 宝石项链 大视野评测
    1082栅栏 大视野评测
    bzoj 1606: [Usaco2008 Dec]Hay For Sale 购买干草
    1639 月度开支 大视野评测
    1650 跳石子 大视野评测
  • 原文地址:https://www.cnblogs.com/lulu638/p/4321894.html
Copyright © 2020-2023  润新知