Struts2系统学习(15)Struts2校验框架基本原理分析

来源:互联网 发布:淘宝上买什么 编辑:程序博客网 时间:2024/05/17 06:57

15 Struts2校验框架基本原理分析

  Struts2提供的验证框架,功能强大而且简单易用。那么一个好的验证框架需要考虑哪些因素呢?
  
1. 验证功能的复用性
  比如都是对一个int数据类型的验证,验证的是它的数据范围,如果验证功能抽象的好,就可以复用同样的验证功能,省去重复开发的麻烦。
2. 验证功能的可扩展性
  是不是可以自己扩展验证功能,并保证扩展功能和原有的框架功能一样使用。
3. 验证与业务逻辑分离
  在业务开发时,可能需要在业务逻辑不变的情况下修改验证逻辑,比如某个网站要求大于18周岁的公民才能注册,随着业务的开展,要修改为大于15岁的公民才能注册,很显然,这个时候,注册逻辑本身没有改变,但是验证逻辑发生了变化,那么,分离的验证逻辑可以保证在修改验证逻辑的时候,不会为业务逻辑带来麻烦。

  针对上一节Struts2系统学习(14)输入校验-基于XML配置方式实现校验的案例,来了解下Struts2校验框架。
  首先要明确输入验证也是通过拦截器interceptor实现的。打开struts2-core-xxx.jar下的struts-default.xml,查看默人拦截器栈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="datetime"/>       <interceptor-ref name="multiselect"/>       <interceptor-ref name="staticParams"/>       <interceptor-ref name="actionMappingParams"/>       <!-- 将HTTP请求中包含的参数值设置到Action中 -->       <interceptor-ref name="params"/>       <!-- 从ActionContext中将转化类型时候发生的错误添加到Action的值域错误中,在校验时候 经常被使用到来显示类型转化错误的信息。 -->       <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-ref name="deprecation"/></interceptor-stack>    

  params拦截器和conversionError拦截器在validation拦截器的前面。params拦截器将请求的参数反射注入到Action的属性;
  conversionError拦截器验证Action的属性是否符合条件。
  validation拦截器会根据配置文件的配置进行验证,其实,validation验证的是值栈中的内容(params注入),而值栈中的内容则来源于请求的参数部分。
  验证框架运行流程:

        这里写图片描述

再看下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">--><!-- 元素validators中可以包含一到多个field或validator元素,即有两种验证类型 --><!ELEMENT validators (field|validator)+><!-- field中至少包含一个field-validator元素 --><!ELEMENT field (field-validator+)><!-- field属性name是必须的,类型是字符串 --><!ATTLIST field                        name CDATA #REQUIRED><!-- 元素field-validator包含零个或多个param、每个param之后必须有一个message属元素(如果有param,必须在message之前) --><!ELEMENT field-validator (param*, message)><!-- field-validator有两个属性值,第一个是type必须存在,第二个属性是short-circuit,值为true或者false。默认值为false --><!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"><!-- 表示只有 PCDATA 的元素,PCDATA表示会被解析的字符,比如param代表年龄,<param name='age'>18</param>那么这个18会被解析为整型 --><!ELEMENT param (#PCDATA)><!-- param中的属性name必须存在,且是字符型 --><!ATTLIST param    name CDATA #REQUIRED   ><!ELEMENT message (#PCDATA|param)*><!-- message中有key属性,可选,key代表国际化信息的表示。--><!ATTLIST message    key CDATA #IMPLIED     >

  分析完dtd文件,就清楚了xml文件中为什么要这样写。校验框架可能存在的两种情况:一种是字段优先校验,另一种是校验器优先校验

<!ELEMENT validators (field|validator)+>

  struts2字段优先校验与校验器优先校验。

  字段校验器配置格式:

<field name="被校验的字段">       <field-validator type="校验器名">             <!--此处需要为不同校验器指定数量不等的校验规则-->             <param name="参数名">参数值</param>                  ....................              <!--校验失败后的提示信息,其中key指定国际化信息的key-->              <message key="I18Nkey">校验失败后的提示信息</message>              <!--校验失败后的提示信息:建议用getText("I18Nkey"),否则可能出现Freemarker template Error-->       </field-vallidator>       <!-- 如果校验字段满足多个规则,下面可以配置多个校验器--></field>

  非字段校验器配置格式:

<validator type="校验器名">          <param name="fieldName">需要被校验的字段</param>          <!--此处需要为不同校验器指定数量不等的校验规则-->                   <param name="参数名">参数值</param>          <!--校验失败后的提示信息,其中key指定国际化信息的key-->          <message key="I18Nkey">校验失败后的提示信息</message>          <!--校验失败后的提示信息:建议用getText("I18Nkey"),否则可能出现Freemarker template Error--></validator>



  在Struts2应用中,如果对action的输入验证出现错误,应用会自动转发到input视图中,这是如何做到的呢?

  (1)通过struts2-core-2.3.24.jar中的struts-default.xml文件我们可以看到:

<package name="struts-default" abstract="true">    ...    <interceptor-stack name="defaultStack">    ...            <!-- 验证框架Interceptor -->            <interceptor-ref name="validation">                  <param name="excludeMethods">input,back,cancel,browse</param>            </interceptor-ref>            <!-- 工作流Interceptor -->            <interceptor-ref name="workflow">                  <param name="excludeMethods">input,back,cancel,browse</param>        </interceptor-ref>    </interceptor-stack>    ...

  此处的指出的两个Interceptor和我们讨论的问题有关系。打开struts-default.xml,找到两个拦截器所对应的类:

<interceptor name="validation" class="org.apache.struts2.interceptor.validation.AnnotationValidationInterceptor"/><interceptor name="workflow" class="com.opensymphony.xwork2.interceptor.DefaultWorkflowInterceptor"/>

  (2)分析AnnotationValidationInterceptor:
  AnnotationValidationInterceptor继承自ValidationInterceptor(com.opensymphony.xwork2.validator),这两个类我们抖需要分析。在AnnotationValidationInterceptor中的doIntercept方法中我们看到:

 protected String doIntercept(ActionInvocation invocation) throws Exception {      Object action = invocation.getAction();      //此处框架只是用了Annotation的方式来查看,现在触发Action方式是否不需要验证的,如果不需要就直接触发,否则使用父类的方法,注意下面的代码简写了      if (action != null) {          Method method = getActionMethod(action.getClass(), invocation.getProxy().getMethod());          SkipValidation skip = (SkipValidation)method.getAnnotation(SkipValidation.class);          if (skip != null) {              return invocation.invoke();//直接触发          }      }      return super.doIntercept(invocation);//使用父类的方法} 

  (3)分析父类ValidationInterceptor的方法:

protected String doIntercept(ActionInvocation invocation) throws Exception {  doBeforeInvocation(invocation);  return invocation.invoke();}

  此处有个核心方法doBeforeInvocation(invocation);,注意是在触发Action之前执行的方法,这也是为什么验证框架不通过就进不了Action方法的原因所在。

protected void doBeforeInvocation(ActionInvocation invocation) throws Exception {        Object action = invocation.getAction();        ActionProxy proxy = invocation.getProxy();        //the action name has to be from the url, otherwise validators that use aliases, like        //MyActio-someaction-validator.xml will not be found        String context = this.getValidationContext(proxy);        String method = proxy.getMethod();        // 省略了log        if (declarative) {       // 根据已经配置好的验证规则(XML)来对action指定的方法进行验证,如果出现错误就会addFieldError           if (validateAnnotatedMethodOnly) {               actionValidatorManager.validate(action, context, method);           } else {               // 对action的所有方法进行验证               actionValidatorManager.validate(action, context);           }       }            // 如果action本身也实现类Validateable接口,则还需要执行action中的validate方法        if (action instanceof Validateable && programmatic) {            // keep exception that might occured in validateXXX or validateDoXXX            Exception exception = null;             Validateable validateable = (Validateable) action;            // log ...            try {                PrefixMethodInvocationUtil.invokePrefixMethod(                                invocation,                                 new String[] { VALIDATE_PREFIX, ALT_VALIDATE_PREFIX });            }            catch(Exception e) {                // If any exception occurred while doing reflection, we want                 // validate() to be executed                if (LOG.isWarnEnabled()) {                    LOG.warn("an exception occured while executing the prefix method", e);                }                exception = e;            }            // 默认alwaysInvokeValidate = true;            if (alwaysInvokeValidate) {            // 此处调用action的validate()方法                validateable.validate();            }            if (exception != null) {                 // rethrow if something is wrong while doing validateXXX / validateDoXXX                 throw exception;            }        }  }

  (4)到此为止我们已经知道验证器是如何工作的,如何添加验证错误的了,以及验证的顺序。梳理下工作原理:
  Struts2默认的defaultStack中包含验证validation拦截器,拦截请求后,在doIntercept(invocation) 中先判断action是否需要进行验证,如果不需要,则调用invocation.invoke()直接触发;如果需要验证,则调用该父类的doIntercept(invocation)方法,父类的doIntercept方法中,先执行doBeforeInvocation(invocation),在其中根据已经配置好的验证规则(XML)来对action进行验证,如果出现错误就会addFieldError,最后判断如果action本身也实现类验证接口Validateable,则执行action中的validate方法,完成最后的验证,如果以上验证成功,此时doBeforeInvocation(invocation)完成调用,最后调用invocation.invoke(),进行下一步的处理。

  那么回到我们的问题,Struts2是怎么回到原有页面(input视图)的?这就是DefaultWorkflowInterceptor的功能,来看代码:

public class DefaultWorkflowInterceptor extends MethodFilterInterceptor {  ...  // 默认验证失败防护"input"视图  private String resultName = Action.INPUT;  ...  protected String doIntercept(ActionInvocation invocation) throws Exception {        Object action = invocation.getAction();    /* 如果需要修改验证失败返回的视图,action需要实现ValidationAware接口,实际应用中,可直接继承     * ActionSupport类,它已经实现了该接口,由此可见:需要对action进行输入校验,必须要将此action     * 实现ValidationAware接口,这样才能获取到验证中出现的错误信息!     */        if (action instanceof ValidationAware) {            ValidationAware validationAwareAction = (ValidationAware) action;            if (validationAwareAction.hasErrors()) {                // log...                String resultName = inputResultName;                /*                 * action可以实现了相关接口或在方法中利用annotion,可以改变验证失败返回的视图                 * 例如:                 *  @InputConfig(resultName="errorValidate")         *      public String execute() {}         * 所以此处需要重新获取返回的视图名称。如果没有设置,则返回的还是"input"                 */                resultName = processValidationWorkflowAware(action, resultName);                resultName = processInputConfig(action, invocation.getProxy().getMethod(), resultName);                resultName = processValidationErrorAware(action, resultName);                return resultName;            }        }    // 如果action没有实现ValidationAware接口,或者实现了该接口但校验没有错误,则进行下一步处理        return invocation.invoke();}

  总结下返回到验证错误的视图的流程:
  在workflow拦截器中,如果被拦截的action实现类ValidationAware接口,则去判断是否存在action errors 或 field errors,如果存在,会根据action实现的相关接口或annotion的设置获取返回的视图名称,据此转发到错误视图。

  由于自己学习struts2不久,因此源代码的分析不能太深入(随着学习的深入,会重新分析),可能会有错误,还希望大家予以指正,谢谢!

  参考:
1. Struts2 校验框架
2. struts2字段校验器与非字段校验器的区别

转载请注明出处:http://blog.csdn.net/mark_lq/article/details/49837507

1 0
原创粉丝点击