struts2

jsp

   JSP要经过两次的“编码”,第一阶段会用pageEncoding,第二阶段会用utf-8的.Java文件至utf-8的.class文件,第三阶段就是由Tomcat传回浏览器的网页, 用的是contentType。

  可以简单认为是,pageEncoding是jsp文件本身的编码;contentType的charset是指服务器发送给客户端时的内容编码。例如:pageEncoding="GBK"。这句话的意思是,告诉JVM 这个jsp本身采用的"GBK"编码,在JSP编译成Servlet传给JVM的时候,就用“GBK”的编码方式将Jsp网页源文件翻译成统一的UTF-8形式的Java字节码。如果不加设定,则JVM默认的用ISO-8859-1这种编码方式。contentType里的charset=gbk,指的是此网页文件输出到浏览器的输出方式为gbk。在这个过程中,一个JSP的源文件需要经过三个阶段,两次编码,才能完成一次完整的输出。

  第一阶段:将jsp编译成Servlet(.java)文件。用到的指令是pageEncoding,根据pageEncoding=“XXX”的指示,找到编码的规则为“XXX”,服务器在将JSP文件编译成.java文件时会根据pageEncoding的设定读取jsp,结果是由指定的编码方案翻译成统一的UTF-8编码的JAVA源码(即.java)。
  第二阶段:从Servlet文件(.java)到Java字节码文件(.class),从UTF-8到UTF-8。在这一阶段中,不论JSP编写时候用的是什么编码方案,经过这个阶段的结果全部是UTF-8的encoding的java源码。JAVAC用UTF-8的encoding读取java源码,编译成UTF-8编码的二进制码(即.class),这是JVM对常数字串在二进制码(Java encoding)内表达的规范。这一过程是由JVM的内在规范决定的,不受外界控制。
  第三阶段:从服务器到浏览器,这在一过程中用到的指令是contentType。服务器载入和执行由第二阶段生成出来JAVA二进制码,输出的结果,也就是在客户端可见到的结果,在这次输出过程中,由contentType属性中的charset来指定,将UTF8形式的二进制码以charset的编码形式来输出。如果没有人为设定,则默认的是ISO-8859-1的形式。

  可见,pageEncoding和contentType都可以设置JSP源文件和响应正文中的字符集编码。但也有区别:
  设置JSP源文件字符集时,优先级为pageEncoding>contentType。如果都没有设置,默认ISO-8859-1。
  设置响应输出的字符集时,优先级为contentType>pageEncoding。如果都没有设置,默认ISO-8859-1。

struts2

一,自定义类型转化器,只有在表单提交到对应字段和对应字段在回显时才会起作用。

  转换器中报异常的话并不会返回到input的result,而只有出现error时才会

二,验证器,只要Action使用到了对应的字段,就回去验证,包活在表单提交时。

  1.modelDriver拦截器的拦截过程(若栈顶对象为null,那么栈顶对象依旧是Action类对象)

    @Override
    public String intercept(ActionInvocation invocation) throws Exception {
        Object action = invocation.getAction();

        if (action instanceof ModelDriven) {
            ModelDriven modelDriven = (ModelDriven) action;
            ValueStack stack = invocation.getStack();
            Object model = modelDriven.getModel();
            if (model !=  null) {
                stack.push(model);
            }
            if (refreshModelBeforeResult) {
                invocation.addPreResultListener(new RefreshModelBeforeResult(modelDriven, model));
            }
        }
        return invocation.invoke();
    }

   2.ExceptionMappingInterceptor

    声明式异常处理。

    在struts.xml中<action>节点下<exception-mapping result="input" exception="java.lang.RuntimeException"></exception-mapping>

    或者在<package>节点下声明整个包的异常映射

      <global-exception-mappings>
        <exception-mapping result="" exception=""></exception-mapping>
      </global-exception-mappings>

    那么若在exception拦截器之后的执行过程中出现<exception-mapping>中配置的异常,那么直接将结果转到对应配置的result。并将ExceptionHolder对象放到栈顶

    拦截器的拦截方法为下

    @Override
    public String intercept(ActionInvocation invocation) throws Exception {
        String result;

        try {
            result = invocation.invoke();
        } catch (Exception e) {
            if (isLogEnabled()) {
                handleLogging(e);
            }
            List<ExceptionMappingConfig> exceptionMappings = invocation.getProxy().getConfig().getExceptionMappings();
            ExceptionMappingConfig mappingConfig = this.findMappingFromExceptions(exceptionMappings, e);
            if (mappingConfig != null && mappingConfig.getResult()!=null) {
                Map parameterMap = mappingConfig.getParams();
                // create a mutable HashMap since some interceptors will remove parameters, and parameterMap is immutable
                invocation.getInvocationContext().setParameters(new HashMap<String, Object>(parameterMap));
                result = mappingConfig.getResult();
                publishException(invocation, new ExceptionHolder(e));
            } else {
                throw e;
            }
        }

        return result;
    }
CRUD    
    defaultStack(即默认拦截顺序)(自上而下的依次拦截)
        <interceptor-stack name="defaultStack">
                <interceptor-ref name="exception"/>
                <interceptor-ref name="alias"/>
                <interceptor-ref name="servletConfig"/>
                <interceptor-ref name="i18n"/>
                <interceptor-ref name="prepare"/>
                <interceptor-ref name="chain"/>
                <interceptor-ref name="scopedModelDriven"/>
                <interceptor-ref name="modelDriven"/>
                <interceptor-ref name="fileUpload"/>
                <interceptor-ref name="checkbox"/>
                <interceptor-ref name="multiselect"/>
                <interceptor-ref name="staticParams"/>
                <interceptor-ref name="actionMappingParams"/>
                <interceptor-ref name="params">
                    <param name="excludeParams">dojo..*,^struts..*,^session..*,^request..*,^application..*,^servlet(Request|Response)..*,parameters...*</param>
                </interceptor-ref>
                <interceptor-ref name="conversionError"/>
                <interceptor-ref name="validation">
                    <param name="excludeMethods">input,back,cancel,browse</param>
                </interceptor-ref>
                <interceptor-ref name="workflow">
                    <param name="excludeMethods">input,back,cancel,browse</param>
                </interceptor-ref>
                <interceptor-ref name="debugging"/>
            </interceptor-stack>
    paramsPrepareParamsStack的拦截顺序
        <interceptor-stack name="paramsPrepareParamsStack">
                <interceptor-ref name="exception"/>
                <interceptor-ref name="alias"/>
                <interceptor-ref name="i18n"/>
                <interceptor-ref name="checkbox"/>
                <interceptor-ref name="multiselect"/>
                <interceptor-ref name="params">
                    <param name="excludeParams">dojo..*,^struts..*,^session..*,^request..*,^application..*,^servlet(Request|Response)..*,parameters...*</param>
                </interceptor-ref>
                <interceptor-ref name="servletConfig"/>
                <interceptor-ref name="prepare"/>
                <interceptor-ref name="chain"/>
                <interceptor-ref name="modelDriven"/>
                <interceptor-ref name="fileUpload"/>
                <interceptor-ref name="staticParams"/>
                <interceptor-ref name="actionMappingParams"/>
                <interceptor-ref name="params">
                    <param name="excludeParams">dojo..*,^struts..*,^session..*,^request..*,^application..*,^servlet(Request|Response)..*,parameters...*</param>
                </interceptor-ref>
                <interceptor-ref name="conversionError"/>
                <interceptor-ref name="validation">
                    <param name="excludeMethods">input,back,cancel,browse</param>
                </interceptor-ref>
                <interceptor-ref name="workflow">
                    <param name="excludeMethods">input,back,cancel,browse</param>
                </interceptor-ref>
            </interceptor-stack>

类型转换错误的显示及自定义
    什么是类型转换?
    jsp页面中提交的字段的类型都是String或者String[]的,当Action类或者model类中接受对应字段的属性不为String时就要进行类型转换
    如何显示错误消息?
    1). Action类要实现ValidationAware接口(因为ActionSupport实现了接口,所以继承ActionSupport即可)
    2). jsp页面中的表单的主体不为simple即可
    问题1: 如何覆盖默认的错误消息?
    1). 在对应的 Action 类所在的包中新建  
        ActionClassName.properties 文件, ActionClassName 即为包含着输入字段的 Action 类的类名
    2). 在属性文件中添加如下键值对: invalid.fieldvalue.fieldName=xxx(表单项的名字)
    
    
    问题2: 如果是 simple 主题, 还会自动显示错误消息吗? 如果不会显示, 怎么办 ?
    1). 通过 debug 标签, 可知若转换出错, 则在值栈的 Action(实现了 ValidationAware 接口) 对象中有一个  fieldErrors 属性.
    该属性的类型为 Map<String, List<String>> 键: 字段(属性名), 值: 错误消息组成的 List. 所以可以使用 LE 或 OGNL 的方式
    来显示错误消息: ${fieldErrors.age[0]}
    
    2). 还可以使用 s:fielderror 标签来显示. 可以通过 fieldName 属性显示指定字段的错误.
    
    问题3. 若是 simple 主题, 且使用  <s:fielderror fieldName="age"></s:fielderror> 来显示错误消息, 则该消息在一个 
    ul, li, span 中. 如何去除 ul, li, span 呢 ?
    在 template.simple 下面的 fielderror.ftl 定义了 simple 主题下, s:fielderror 标签显示错误消息的样式. 所以修改该
    配置文件即可. 在 src 下新建  template.simple 包, 新建 fielderror.ftl 文件, 把原生的 fielderror.ftl 中的内容
    复制到新建的 fielderror.ftl 中, 然后剔除 ul, li, span 部分即可. 
    
这里定义的类型转换器,仅是在表单的提交和回显时才会进过转换器。
若想使页面上的Date类型按指定格式显示在页面上,则可以

    问题4. 如何自定义类型转换器 ?  
    1). 为什么需要自定义的类型转换器 ? 因为 Struts 不能自动完成 字符串 到 引用类型 的 转换.
    2). 如何定义类型转换器:
    I.  开发类型转换器的类: 扩展 StrutsTypeConverter 类.
    II. 配置类型转换器: 
    有两种方式
    ①. 基于字段的配置: 
        > 在字段所在的 Model(可能是 Action, 可能是一个 JavaBean) 的包下, 新建一个 ModelClassName-conversion.properties 文件
        > 在该文件中输入键值对: fieldName=类型转换器的全类名. 
        > //第一次使用该转换器时创建实例. 
        > 类型转换器是单实例的!    
    
    ②. 基于类型的配置:
        > 在 src 下新建 xwork-conversion.properties
        > 键入: 待转换的类型=类型转换器的全类名.(java.util.Date=conversion.DateConversion 类型须为全称)
        > //在当前 Struts2 应用被加载时创建实例.
            //(所以在它的构造函数中不能用ServletActionContext.getServletContext().getInitParameter("dateForm");)
        
        //切记:当字符串切割“.”时血的教训(正则,要写成“\.”)
        
        form 标签的 name 属性可以被映射到一个属性的属性.如Manger.birth
        Struts 还允许填充 Collection 里的对象, 这常见于需要快速录入批量数据的场合
            例:表单中标签名字为mgrs[0,1,2,3...].birth        Action中属性为private Collection<Manager> mgrs = null;即可
        
        配置整个App的初始化参数?
                    在Web.xml中
                    <context-param>
                            <param-name>dateForm</param-name>
                            <param-value>yyyy年MM月dd日 hh:mm:ss</param-value>
                    </context-param>
        
国际化的目标

    1). 如何配置国际化资源文件
    
    I.   Action 范围资源文件: 在Action类文件所在的路径建立名为 ActionName_language_country.properties 的文件
    II.  包范围资源文件: 在包的根路径下建立文件名为 package_language_country.properties 的属性文件,
    一旦建立,处于该包下的所有 Action 都可以访问该资源文件。注意:包范围资源文件的 baseName 就是package,不是Action所在的包名。
    III. 全局资源文件
        > 命名方式: basename_language_country.properties
        > struts.xml <constant name="struts.custom.i18n.resources" value="baseName"/>
    
    IV.  国际化资源文件加载的顺序如何呢 ? 离当前 Action 较近的将被优先加载. 
    
    假设我们在某个 ChildAction 中调用了getText("username"):
    
    (1) 加载和 ChildAction 的类文件在同一个包下的系列资源文件 ChildAction.properties
    (2) 加载  ChildAction 实现的接口 IChild,且和 IChildn 在同一个包下 IChild.properties 系列资源文件。
    (3) 加载 ChildAction 父类 Parent,且和 Parent 在同一个包下的 baseName 为 Parent.properties 系列资源文件。
    (4) 若 ChildAction 实现 ModelDriven 接口,则对于getModel()方法返回的model 对象,重新执行第(1)步操作。
    (5) 查找当前包下 package.properties 系列资源文件。
    (6) 沿着当前包上溯,直到最顶层包来查找 package.properties 的系列资源文件。
    (7) 查找 struts.custom.i18n.resources 常量指定 baseName 的系列资源文件。
    (8) 直接输出该key的字符串值。
    
    
    2). 如何在页面上 和 Action 类中访问国际化资源文件的  value 值
    
    I. 在 Action 类中. 若 Action 实现了 TextProvider 接口, 则可以调用其 getText() 方法获取 value 值
        > 通过继承 ActionSupport 的方式。 
        
    II. 页面上可以使用 s:text 标签; 对于表单标签可以使用表单标签的 key 属性值
        > 若有占位符, 则可以使用 s:text 标签的 s:param 子标签来填充占位符
        > 可以利用标签和 OGNL 表达式直接访问值栈中的属性值(对象栈 和 Map 栈)
        
        time=Time:{0}
        
        <s:text name="time">
            <s:param value="date"></s:param>
        </s:text>
    
        ------------------------------------
        
        time2=Time:${date}
        
        <s:text name="time2"></s:text>
        
    
    3). 实现通过超链接切换语言. 
    
    I.  关键之处在于知道 Struts2 框架是如何确定 Local 对象的 !
    II. 可以通过阅读 I18N 拦截器知道. 
    III. 具体确定 Locale 对象的过程:
    
        > Struts2 使用 i18n 拦截器 处理国际化,并且将其注册在默认的拦截器栈中
        > i18n拦截器在执行Action方法前,自动查找请求中一个名为 request_locale 的参数。
              如果该参数存在,拦截器就将其作为参数,转换成Locale对象,并将其设为用户默认的Locale(代表国家/语言环境)。
              并把其设置为 session 的 WW_TRANS_I18N_LOCALE 属性
        > 若 request 没有名为request_locale 的参数,则 i18n 拦截器会从 Session 中获取 WW_TRANS_I18N_LOCALE 的属性值,
             若该值不为空,则将该属性值设置为浏览者的默认Locale 
        > 若 session 中的 WW_TRANS_I18N_LOCALE 的属性值为空,则从 ActionContext 中获取 Locale 对象。
        
    IV.  具体实现: 只需要在超连接的后面附着  request_locale 的请求参数, 值是 语言国家 代码.
        <a href="testI18n.action?request_locale=en_US">English</a>
        <a href="testI18n.action?request_locale=zh_CN">中文</a>
        
        > 注意: 超链接必须是一个 Struts2 的请求, 即使 i18n 拦截器工作!
    //注意<a href="index.jsp?request_locale=en_US">English</a>此请求是不经过struts拦截器的
    
Struts2 的验证
    1). 验证分为两种:
    
        > 声明式验证*
        
            >> 对哪个 Action 或 Model 的那个字段进行验证
            >> 使用什么验证规则
            >> 如果验证失败, 转向哪一个页面, 显示是什么错误消息
        
        > 编程式验证
        
    2). 声明式验证的 helloworld
    
    I.  先明确对哪一个 Action 的哪一个字段进行验证: age
    II. 编写配置文件: 
        > 把 struts-2.3.15.3appsstruts2-blankWEB-INFclassesexample 下的 Login-validation.xml 文件复制到
        当前 Action 所在的包下. 
        > 把该配置文件改为: 把  Login 改为当前 Action 的名字. 
        > 编写验证规则: 参见 struts-2.3.15.3/docs/WW/docs/validation.html 文档即可.
        > 在配置文件中可以定义错误消息: 
        
            <field name="age">
             <field-validator type="int">
                 <param name="min">20</param>
                 <param name="max">50</param>
                 <message>^^Age needs to be between ${min} and ${max}</message>
             </field-validator>
         </field>
         
         > 该错误消息可以国际化吗. 可以
         
                 <message key="error.int"></message>. 
                 
                 再在 国际化资源文件 中加入一个键值对: error.int=^^^Age needs to be between ${min} and ${max}
        
    III. 若验证失败, 则转向 input 的那个 result. 所以需要配置 name=input 的 result
         <result name="input">/validation.jsp</result>
         
    IV. 如何显示错误消息呢 ?      
    
        > 若使用的是非 simple, 则自动显示错误消息.
        > 若使用的是 simple 主题, 则需要 s:fielderror 标签或直接使用 EL 表达式(使用 OGNL)
        
        ${fieldErrors.age[0] } 
        OR
        <s:fielderror fieldName="age"></s:fielderror>*
    
    3). 注意: 若一个 Action 类可以应答多个 action 请求, 多个 action 请求使用不同的验证规则, 怎么办 ?
    
        > 为每一个不同的 action 请求定义其对应的验证文件: ActionClassName-AliasName-validation.xml
        
        > 不带别名的配置文件: ActionClassName-validation.xml 中的验证规则依然会发生作用. 可以把各个 action 公有的验证规则
        配置在其中. 但需要注意的是, 只适用于某一个 action 的请求的验证规则就不要这里再配置了. 
        
    4). 声明式验证框架的原理:
    
        > Struts2 默认的拦截器栈中提供了一个 validation 拦截器
        
        > 每个具体的验证规则都会对应具体的一个验证器. 有一个配置文件把验证规则名称和验证器关联起来了. 而实际上验证的是那个验证器. 
        该文件位于 com.opensymphony.xwork2.validator.validators 下的 default.xml
        
        <validator name="required" class="com.opensymphony.xwork2.validator.validators.RequiredFieldValidator"/>
    
    5). 短路验证: 若对一个字段使用多个验证器, 默认情况下会执行所有的验证. 若希望前面的验证器验证没有通过, 后面的就不再验证, 可以使用短路验证
    
            <!-- 设置短路验证: 若当前验证没有通过, 则不再进行下面的验证 -->
            <field-validator type="conversion" short-circuit="true">
                <message>^Conversion Error Occurred</message>
            </field-validator>
    
            <field-validator type="int">
                <param name="min">20</param>
                <param name="max">60</param>
                <message key="error.int"></message>
            </field-validator>    
            
    6). 若类型转换失败, 默认情况下还会执行后面的拦截器, 还会进行 验证. 可以通过修改 ConversionErrorInterceptor 源代码的方式使
    当类型转换失败时, 不再执行后续的验证拦截器, 而直接返回 input 的 result
    
                    Object action = invocation.getAction();
            if (action instanceof ValidationAware) {
                ValidationAware va = (ValidationAware) action;
    
                if(va.hasFieldErrors() || va.hasActionErrors()){
                    return "input";
                }
            }    
            
    7). 关于非字段验证: 不是针对于某一个字段的验证. 
    
        <validator type="expression">
            <param name="expression"><![CDATA[password==password2]]></param>
            <message>Password is not equals to password2</message>
        </validator>
         
              显示非字段验证的错误消息, 使用 s:actionerror 标签:  <s:actionerror/>
              
    8). 不同的字段使用同样的验证规则, 而且使用同样的响应消息 ?
    
    error.int=${getText(fieldName)} needs to be between ${min} and ${max}
    
    age=u5E74u9F84
    count=u6570u91CF       
    
    详细分析参见  PPT 159.  
    
    9). 自定义验证器:
    
    I.   定义一个验证器的类
    
        > 自定义的验证器都需要实现 Validator. 
        > 可以选择继承 ValidatorSupport 或 FieldValidatorSupport 类
        > 若希望实现一个一般的验证器, 则可以继承 ValidatorSupport
        > 若希望实现一个字段验证器, 则可以继承 FieldValidatorSupport
        
        > 具体实现可以参考目前已经有的验证器. 
        
        > 若验证程序需要接受一个输入参数, 需要为这个参数增加一个相应的属性
    
    II.  在配置文件中配置验证器
    
        > 默认情况下, Struts2 会在 类路径的根目录下(src中)加载 validators.xml 文件. 在该文件中加载验证器.
             该文件的定义方式同默认的验证器的那个配置文件: 位于 com.opensymphony.xwork2.validator.validators 下的 default.xml
             
        > 若类路径下没有指定的验证器, 则从 com.opensymphony.xwork2.validator.validators 下的 default.xml 中的验证器加载     
    
    III. 使用: 和目前的验证器一样. 
    
    IV. 示例代码: 自定义一个 18 位身份证验证器
    
1. 文件的上传:

    1). 表单需要注意的 32). Struts2 的文件上传实际上使用的是 Commons FileUpload 组件, 所以需要导入
    
    commons-fileupload-1.3.jar
    commons-io-2.0.1.jar
    
    3). Struts2 进行文件上传需要使用 FileUpload 拦截器
    
    4). 基本的文件的上传: 直接在 Action 中定义如下 3 个属性, 并提供对应的 getter 和 setter
    
    //文件对应的 File 对象
    private File [fileFieldName];
    //文件类型
    private String [fileFieldName]ContentType;
    //文件名
    private String [fileFieldName]FileName;
    
    5). 使用 IO 流进行文件的上传即可. 
    
    6). 一次传多个文件怎么办 ?
    
    若传递多个文件, 则上述的 3 个属性, 可以改为 List 类型! //多个文件域的 name 属性值需要一致. 
    
    7). 可以对上传的文件进行限制吗 ? 例如扩展名, 内容类型, 上传文件的大小 ? 若可以, 则若出错, 显示什么错误消息呢 ? 消息可以定制吗 ? 
    
    可以的!
    
    可以通过配置 FileUploadInterceptor 拦截器的参数的方式来进行限制
    
    maximumSize (optional) - 默认的最大值为 2M. 上传的单个文件的最大值
    
    allowedTypes (optional) - 允许的上传文件的类型. 多个使用 , 分割
    
    allowedExtensions (optional) - 允许的上传文件的扩展名. 多个使用 , 分割.
    
    注意: 在 org.apache.struts2 下的 default.properties 中有对上传的文件总的大小的限制. 可以使用常量的方式来修改该限制
    
    struts.multipart.maxSize=2097152
    
    定制错误消息. 可以在国际化资源文件中定义如下的消息:
    
    struts.messages.error.uploading - 文件上传出错的消息
    
    struts.messages.error.file.too.large - 文件超过最大值的消息
    
    struts.messages.error.content.type.not.allowed - 文件内容类型不合法的消息
    
    struts.messages.error.file.extension.not.allowed - 文件扩展名不合法的消息
    
    问题: 此种方式定制的消息并不完善. 可以参考 org.apache.struts2 下的 struts-messages.properties, 可以提供更多的定制信息.

2. 文件的下载:

    1). Struts2 中使用 type="stream" 的 result 进行下载即可
    
    2). 具体使用细节参看 struts-2.3.15.3-all/struts-2.3.15.3/docs/WW/docs/stream-result.html
    
    3). 可以为 stream 的 result 设定如下参数
    
    contentType: 结果类型
    contentLength: 下载的文件的长度
    contentDisposition: 设定 Content-Dispositoin 响应头. 该响应头指定接应是一个文件下载类型, 一般取值为  attachment;filename="document.pdf".
    
    inputName: 指定文件输入流的 getter 定义的那个属性的名字. 默认为 inputStream
    
    bufferSize: 缓存的大小. 默认为 1024
    allowCaching: 是否允许使用缓存 
    contentCharSet: 指定下载的字符集 
    
    4). 以上参数可以在 Action 中以 getter 方法的方式提供!

3. 表单的重复提交问题

    1). 什么是表单的重复提交
    
        > 在不刷新表单页面的前提下:?
            >> 多次点击提交按钮
            >> 已经提交成功, 按 "回退" 之后, 再点击 "提交按钮".
            >> 在控制器响应页面的形式为转发情况下,若已经提交成功, 然后点击 "刷新(F5)"
            
        > 注意:
            >> 若刷新表单页面, 再提交表单不算重复提交
            >> 若使用的是 redirect 的响应类型, 已经提交成功后, 再点击 "刷新", 不是表单的重复提交
            
    2). 表单重复提交的危害:              
    
    3). Struts2 解决表单的重复提交问题:
    
    I. 在 s:form 中添加 s:token 子标签
    
        > 生成一个隐藏域
        > 在 session 添加一个属性值
        > 隐藏域的值和 session 的属性值是一致的. 
        
    II. 使用 Token 或 TokenSession 拦截器. 
    
        > 这两个拦截器均不在默认的拦截器栈中, 所以需要手工配置一下
        > 若使用 Token 拦截器, 则需要配置一个 invalid.token 的 result ; 且Action类要实现 TextProvider 接口,否则会报空指针异常,简单的方法是继承ActionSupport
        > 若使用 TokenSession 拦截器, 则不需要配置任何其它的 result
        
    III. Token VS TokenSession
    
        > 都是解决表单重复提交问题的
        > 使用 token 拦截器会转到 token.valid 这个 result
        > 使用 tokenSession 拦截器则还会响应那个目标页面, 但不会执行 tokenSession 的后续拦截器. 就像什么都没发生过一样!
        
    IV. 可以使用 s:actionerror 标签来显示重复提交的错误消息. 
    该错误消息可以在国际化资源文件中覆盖. 该消息可以在 struts-messages.properties 文件中找到
    
    struts.messages.invalid.token=^^The form has already been processed or no token was supplied, please try again.

4. 自定义拦截器

    1). 具体步骤
    
    I. 定义一个拦截器的类
    
        > 可以实现 Interceptor 接口
        > 继承 AbstractInterceptor 抽象类
    
    II. 在 struts.xml 文件配置.    
    
        <interceptors>
                
            <interceptor name="hello" class="com.atguigu.struts2.interceptors.MyInterceptor"></interceptor>
            
        </interceptors>
        
        <action name="testToken" class="com.atguigu.struts2.token.app.TokenAction">
            <interceptor-ref name="hello"></interceptor-ref>
            <interceptor-ref name="defaultStack"></interceptor-ref>
            <result>/success.jsp</result>
            <result name="invalid.token">/token-error.jsp</result>
        </action>
        
    III. 注意: 在自定义的拦截器中可以选择不调用 ActionInvocation 的 invoke() 方法. 那么后续的拦截器和 Action 方法将不会被调用.
    Struts 会渲染自定义拦截器 intercept 方法返回值对应的 result
原文地址:https://www.cnblogs.com/feifeiyun/p/6415881.html