• Struts2学习笔记(四) Action(中)


    前面说道实现Action一般选择继承ActionSupport的方式,因为它提供了一些额外的功能,比如基本的数据验证和访问本地信息。

    基本数据验证

    由于ActionSupport类实现了Validateable接口,那么在该动作被触发的时候会在执行动作方法之前先执行validate方法,如果验证没有通过,那么就会返回信息输入结果页面。因此我们只需要在Action中重写validate方法就可以实现数据的验证了。

    public class HelloWorld extends ActionSupport {
    
        private String userName;
    
    
        public String getUserName() {
            return userName;
        }
    
        public void setUserName(String userName) {
            this.userName = userName;
        }
    
        public String execute1() throws Exception {
            
            
            return "success";
        }
    
        @Override
        public void validate() {
            if(userName == null){
                addFieldError("userName","未获取到参数!");
            }else if(userName.length()>5){
                addFieldError("userName","用户名太长");
            }
            
        }
    
    }

    input.jsp

    <body>
    
      <s:if test="hasFieldErrors()">
    
        <s:iterator value="fieldErrors"> 
    
            <font color="#FF0000"><s:property value="value[0]"/></font><br> 
    
        </s:iterator> 
    
      </s:if>
    
      <form action="login.action" method="post">
    
         username : <input type="text" name="userName"/><br/>
    
         password :<input type="password" name="password"><br/>
    
          <input type="submit" value="submit"/>
    
      </form>
    
      </body>

    执行:

    结果:

    这里有几点需要注意:

    (1) 每次执行动作方法之前,都会执行validate方法,如果我们的Action中有多个动作方法的话,那么每个动作方法执行之前都会执行validate方法,因此validate方法中一般执行一些通用的检查。

    (2)  如果validate中没有通过,即产生了错误消息,那么不会执行动作方法,会直接返回”input”。

    下面大致了解一下validate方法执行的原理。首先我们需要知道,这个方法是被拦截器调用的,拦截器放在动作执行之前,拦截每个访问该Action的请求。这个拦截器叫做Workflow拦截器,查看文档可以知道,该拦截器的实现类为com.opensymphony.xwork2.interceptor.DefaultWorkflowInterceptor。文档中说这个拦截器要做的事情:

    (1)如果Action中有validate{MethodName}()方法,那么执行它

    (2)如果(1)不成立,但是Action中有validateDo{MethodName}()方法,那么执行它

    (3)不管(1)或(2)是否执行,只要这个拦截器的alwaysInvokeValidate属性为true,那么总是会执行validate方法。

    查看DefaultWorkflowInterceptor的源码:

    public class DefaultWorkflowInterceptor extends MethodFilterInterceptor {
    
        private static final long serialVersionUID = 7563014655616490865L;
    
        private static final Logger LOG = LoggerFactory.getLogger(DefaultWorkflowInterceptor.class);
    
        private static final Class[] EMPTY_CLASS_ARRAY = new Class[0];
        
    private String inputResultName = Action.INPUT;
    
        public void setInputResultName(String inputResultName) {
            this.inputResultName = inputResultName;
        }
    
        @Override
        protected String doIntercept(ActionInvocation invocation) throws Exception {
            Object action = invocation.getAction();
    
            if (action instanceof ValidationAware) {
                ValidationAware validationAwareAction = (ValidationAware) action;
    
                if (validationAwareAction.hasErrors()) {
                    if (LOG.isDebugEnabled()) {
                        LOG.debug("Errors on action " + validationAwareAction + ", returning result name 'input'");
                    }
    
                    String resultName = inputResultName;
    
                    if (action instanceof ValidationWorkflowAware) {
                        resultName = ((ValidationWorkflowAware) action).getInputResultName();
                    }
    
                    InputConfig annotation = action.getClass().getMethod(invocation.getProxy().getMethod(), EMPTY_CLASS_ARRAY).getAnnotation(InputConfig.class);
                    if (annotation != null) {
                        if (!annotation.methodName().equals("")) {
                            Method method = action.getClass().getMethod(annotation.methodName());
                            resultName = (String) method.invoke(action);
                        } else {
                            resultName = annotation.resultName();
                        }
                    }
    
    
                    return resultName;
                }
            }
    
            return invocation.invoke();
        }
    
    }

    发现在这个拦截器中根本就没有调用validate方法,而只是对是否产生的错误信息进行了检测。并且看以看到,如果存在错误信息,默认返回的Result是Action.input(”input”)。

    那么既然workflow拦截器没有执行validate方法,由于我们的Action使用的默认的拦截器栈,那么就去看看在workflow拦截器前面的拦截器validation拦截器。

    查看文档可以知道这个拦截器的实现类为:org.apache.struts2.interceptor.validation.AnnotationValidationInterceptor。这个类继承了com.opensymphony.xwork2.validator.ValidationInterceptor这个类查看这个类的源码,里面有一个doBeforInvocation方法:

    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, see WW-3194
            String context = proxy.getActionName();
            String method = proxy.getMethod();
    
            if (log.isDebugEnabled()) {
                log.debug("Validating "
                        + invocation.getProxy().getNamespace() + "/" + invocation.getProxy().getActionName() + " with method "+ method +".");
            }
            
    
            if (declarative) {
               if (validateAnnotatedMethodOnly) {
                   actionValidatorManager.validate(action, context, method);
               } else {
                   actionValidatorManager.validate(action, context);
               }
           }    
            
            if (action instanceof Validateable && programmatic) {
                // keep exception that might occured in validateXXX or validateDoXXX
                Exception exception = null; 
                
                Validateable validateable = (Validateable) action;
                if (LOG.isDebugEnabled()) {
                    LOG.debug("Invoking validate() on action "+validateable);
                }
                
                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;
                }
                
                
                if (alwaysInvokeValidate) {
                    validateable.validate();
                }
                
                if (exception != null) { 
                    // rethrow if something is wrong while doing validateXXX / validateDoXXX 
                    throw exception;
                }
            }
        }

    可以看到,正是在这个方法中对Action中的validate方法进行了调用。为了验证到底validate方法是在validation拦截器中被调用的还是在workflow拦截器中被调用的,我们写个小实例,不使用defaultStack,我们手动为Action配置拦截器栈(这个拦截器栈基本和defaultStack相同):

    <package name="default" namespace="/" extends="struts-default">
            
            <interceptors>
                <interceptor-stack name="luo">
                    <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\..*</param>
                    </interceptor-ref>
                    <interceptor-ref name="conversionError"/>
                   
                    <interceptor-ref name="workflow">
                        <param name="excludeMethods">input,back,cancel,browse</param>
                    </interceptor-ref>
                    <interceptor-ref name="debugging"/>
                    
                </interceptor-stack>
            </interceptors>    
            <default-interceptor-ref name="luo"></default-action-ref>
            <action name="hello" class="action.HelloWorld">
                <result name="success">/success.jsp</result>
                <result name="input">/input.jsp</result>
            </action>
    
    </package>

    我们仅仅是将validation拦截器移出了Action的先前默认的defaultStack拦截器栈中,我们再来执行前面执行过的测试:

    结果:

    success结果被显示了,说明validate方法没有被调用了。那么就说明了validate方法是在validattion拦截器中被调用的。

    workflow拦截器和validation拦截器可以传递一些参数:

    <interceptor-ref name="validation">
          <paramname="excludeMethods">input,back,cancel,browse</param>
    </interceptor-ref>
    

    当执行到与excludeMethods参数中指定的方法同名的方法时,拦截器不会做出反映。还可以通过这种形式来为拦截器指定其他属性,比如为workflow指定默认返回的Result等。

    <interceptor-ref name="workflow">
           <param name="inputResultName">error</param>
           <param name="excludeMethods">*</param>
           <param name="includeMethods">myWorkflowMethod</param>
    </interceptor-ref>
    

    访问本地信息

    在上面的例子程序中,我们将错误信息硬编码在代码中,这样有一些弊端:

    (1)       一是不容易修改,如果我们想改变消息的内容,还得重新修改代码和重新编译类

    (2)       而是不利于国际化,如果要将其中的中文换成英文,那么还得到代码中来修改和重新编译类

    通过访问本地信息,我们将一些消息按照键值对的形式保存在类文件外部的文件,通过key来获取文件中对应的消息,那么一旦我们需要修改消息内容,那么就只需要修改这个外部文件而不需要重新编译类了。基本步骤:

    (1)       首先建立消息文件,在Action类的类路径下建立一个与Action同名的properties文件,例如HelloWorld.properties,然后在文件里按照 key= value的形式添加错误消息。

    nameIsNull =\u672A\u83B7\u53D6\u5230\u53C2\u6570

    nameIsTooLong=\u7528\u6237\u540D\u592A\u957F

    需要注意的是Value的值必须是unicode编码,这样在程序充才能正确获取,在eclipse中可以使用可视化工具编辑properties文件,这样生成的代码会将中文转换为unicode编码。JDK中也提供了native2ascii命令来实现这种转换,具体方法google一下就知道了。

    (2)修改原来在Actin中硬编码的错误消息,改成从配置文件中读取信息。

    public void validate() {

       if(userName ==null){

          addFieldError("userName",getText("nameIsNull"));

       }else if(userName.length()>5){

         addFieldError("userName",getText("nameIsTooLong"));

       }

    }

    重新运行程序,结果和前面一样。至于实现的原理,最终还是借助JDK提供的ResourceBoundle来实现的。ActionSupport类实现了TextProvider接口和LocalProvider接口。当我们调用ActionSupport的getText方法时,其内部实际上是调用了TextProvider一个实现类(TextProviderSupport)的对象上的getText方法。

    public String getText(String aTextName) {

            return getTextProvider().getText(aTextName);

    }

    private TextProvider getTextProvider() {

           if (textProvider ==null) {

               TextProviderFactory tpf = new TextProviderFactory();

               if (container !=null) {

                    container.inject(tpf);

               }

              textProvider = tpf.createInstance(getClass(), this);

           }

           return textProvider;

     }

    由于实现了LocalProvider,因此ActionSupport也是LocalProvider类型,其getLocal从ActionContext中获取Local对象。使用系统默认的Local对象时,ResourceBoundle会在Class所在路径下寻找与当前类同名的properties文件,如果使用指定的Local对象,那么我们的资源文件还需要在名字中添加相应的国家代码,例如:HelloWorld_zh_CN.properties

  • 相关阅读:
    如何正确设置数据库连接池的大小
    spring生命周期
    Spring高级进阶:BeanFactoryPostProcessor
    Spring Bean的生命周期(非常详细)
    表达式求值相关算法
    golang 命令行参数解析 hflag
    golang 标准命令行解析库 flag
    docker consul 环境搭建
    docker etcd 环境搭建
    mysql 事务
  • 原文地址:https://www.cnblogs.com/jdluojing/p/3212451.html
Copyright © 2020-2023  润新知