Struts(二十三):使用声名式验证

  • Struts2工程中的验证分为两种:

1、基于XWork Validation Framework的声明式验证:Struts2提供了一些基于XWork Validation Framework的内建验证程序.使用这些验证不需要编程,只需要在一个xml文件里对验证程序应该如何工作作出声明就可以了,需要声明的内容包括:

  针对哪个Action或者Model的某个或某些字段验证;

  使用什么验证规则;

  如果验证失败,转向哪个页面,显示什么错误信息。

2、编程式验证:通过编写代码实现验证用户输入信息。

  • 声明式验证示例:

1、需要先明确对那个Action或者Model的哪个字段进行验证;

2、编写配置文件:

  把struts-2.3.31-all\struts-2.3.31\apps\struts2-blank\WEB-INF\src\java\example\Login-validation.xml

文件拷贝到对应的包下,并重命名该配置文件为ActionClassName-validation.xml或者ModelClassName-validation.xml

  编写验证规则,参考validation 官网文档:struts-2.3.31-all/struts-2.3.31/docs/docs/validation.html

  在编写文件中可以定义错误消息:

<!DOCTYPE validators PUBLIC
        "-//Apache Struts//XWork Validator 1.0.2//EN"
        "http://struts.apache.org/dtds/xwork-validator-1.0.2.dtd">

<validators>
    <!-- 基于字段的验证 -->
    <field name="age">
        <field-validator type="int">
            <param name="max">180</param>
            <param name="min">1</param>
            <message>Age must to be between ${min} and ${max}.</message>
        </field-validator>
    </field>
</validators>

  是否可以把错误消息进行国际化?可以。

  --第一步:在src目录下创建一个i18n.properties文件,编写文件内容:

ageErrorMsg="Age\u9A8C\u8BC1\u5931\u8D25,\u53D6\u503C\u8303\u56F4\u5FC5\u987B\u4F4D\u4E8E ${min} \u4E0E ${max} \u4E4B\u95F4."

  --第二步:修改struts.xml添加配置:

<constant name="struts.custom.i18n.resources" value="i18n"></constant>

  --第三步:修改com.dx.struts2.myvalidations包下的MyValidationAction-validation.xml:

<!DOCTYPE validators PUBLIC
        "-//Apache Struts//XWork Validator 1.0.2//EN"
        "http://struts.apache.org/dtds/xwork-validator-1.0.2.dtd">

<validators>
    <!-- 基于字段的验证 -->
    <field name="age">
        <field-validator type="int">
            <param name="max">180</param>
            <param name="min">1</param>
            <message key="ageErrorMsg"></message>
        </field-validator>
    </field>
</validators>

  -- 访问index.jsp,并在age框中填写1005,提交:

3、如果验证失败,则转向input的那个result,所以需要配置name=input的result

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
    "http://struts.apache.org/dtds/struts-2.3.dtd">
<struts>
    <constant name="struts.custom.i18n.resources" value="i18n"></constant>
    <package name="default" namespace="/" extends="struts-default">
        <action name="myValidation" class="com.dx.struts2.myvalidations.MyValidationAction">
            <result>/success.jsp</result>
            <result name="input">/index.jsp</result>
        </action>
    </package>
</struts>

4、如何显示错误消息?

  如果使用的是非“simple”主题的form标签,则自动显示错误消息;

  如果使用的是“simple”主题的form标签,则可以使用s:fielderror标签或者EL(OGNL表达式)

    <s:form action="myValidation" method="post" theme="simple">
        <s:textfield name="age" label="Age"></s:textfield>
        <s:fielderror fieldName="age"></s:fielderror>
        ${fieldErrors.age[0] }
        <s:submit label="Submit"></s:submit>
    </s:form>
  • 同一个Action类可以应答多个action请求时,多个action请求使用不同的验证规则,怎么办?

1、为每个不同的action请求定义其对应的验证文件,文件命名规则:ActionClassName-AliasName-validation.xml;

2、不带别名的的配置文件(ActionClassName-validation.xml)中的验证规则依然会起作用,可以把多个action请求公有的验证规则写到该配置文件中,但如果某个验证规则适用某一个action请求,就不要配置到这里。

  示例:

struts.xml

    <package name="default" namespace="/" extends="struts-default">
        <action name="myValidation" class="com.dx.struts2.myvalidations.MyValidationAction">
            <result>/success.jsp</result>
            <result name="input">/index.jsp</result>
        </action>
        <action name="myValidation2" class="com.dx.struts2.myvalidations.MyValidationAction" method="execute2">
            <result>/success.jsp</result>
            <result name="input">/index.jsp</result>
        </action>
    </package>

index.jsp

    <s:form action="myValidation" method="post">
        <s:textfield name="age" label="Age"></s:textfield>
        <s:submit label="Submit"></s:submit>
    </s:form>
    <s:form action="myValidation2" method="post">
        <s:textfield name="age2" label="Age2"></s:textfield>
        <s:submit label="Submit"></s:submit>
    </s:form>

MyValidationAction.java

package com.dx.struts2.myvalidations;
import com.opensymphony.xwork2.ActionSupport;

public class MyValidationAction extends ActionSupport {
    private static final long serialVersionUID = 1L;

    private Integer age;
    private Integer age2;

    public Integer getAge2() {
        return age2;
    }
    public void setAge2(Integer age2) {
        this.age2 = age2;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
    public Integer getAge() {
        return age;
    }

    @Override
    public String execute() throws Exception {
        System.out.println("execute...");
        return SUCCESS;
    }
    
    public String execute2() {
        System.out.println("execute2...");
        return SUCCESS;
    }
}

MyValidationAction-myValidation-validation.xml

<!DOCTYPE validators PUBLIC
        "-//Apache Struts//XWork Validator 1.0.2//EN"
        "http://struts.apache.org/dtds/xwork-validator-1.0.2.dtd">

<validators>
    <!-- 基于字段的验证 -->
    <field name="age">
        <field-validator type="int">
            <param name="max">180</param>
            <param name="min">1</param>
            <message key="ageErrorMsg"></message>
        </field-validator>
    </field>
</validators>
View Code

MyValidationAction-myValidation2-validation.xml

<!DOCTYPE validators PUBLIC
        "-//Apache Struts//XWork Validator 1.0.2//EN"
        "http://struts.apache.org/dtds/xwork-validator-1.0.2.dtd">

<validators>
    <!-- 基于字段的验证 -->
    <field name="age2">
        <field-validator type="int">
            <param name="max">180</param>
            <param name="min">1</param>
            <message key="ageErrorMsg2"></message>
        </field-validator>
    </field>
</validators>
View Code
  • 运行原理分析:

1、调用struts2的拦截器栈中的validation拦截器(org.apache.struts2.interceptor.validation.AnnotationValidationInterceptor)

/*
 * $Id$
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.apache.struts2.interceptor.validation;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;

import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.util.AnnotationUtils;
import com.opensymphony.xwork2.validator.ValidationInterceptor;

import org.apache.struts2.StrutsConstants;

/**
 * Extends the xwork validation interceptor to also check for a @SkipValidation
 * annotation, and if found, don't validate this action method
 */
public class AnnotationValidationInterceptor extends ValidationInterceptor {

    /** Auto-generated serialization id */
    private static final long serialVersionUID = 1813272797367431184L;

    private boolean devMode;

    @Inject(StrutsConstants.STRUTS_DEVMODE)
    public void setDevMode(String devMode) {
        this.devMode = "true".equalsIgnoreCase(devMode);
    }

    protected String doIntercept(ActionInvocation invocation) throws Exception {

        Object action = invocation.getAction();
        if (action != null) {
            Method method = getActionMethod(action.getClass(), invocation.getProxy().getMethod());
            Collection<Method> annotatedMethods = AnnotationUtils.getAnnotatedMethods(action.getClass(), SkipValidation.class);
            if (annotatedMethods.contains(method))
                return invocation.invoke();

            //check if method overwites an annotated method
            Class clazz = action.getClass().getSuperclass();
            while (clazz != null) {
                annotatedMethods = AnnotationUtils.getAnnotatedMethods(clazz, SkipValidation.class);
                if (annotatedMethods != null) {
                    for (Method annotatedMethod : annotatedMethods) {
                        if (annotatedMethod.getName().equals(method.getName())
                                && Arrays.equals(annotatedMethod.getParameterTypes(), method.getParameterTypes())
                                && Arrays.equals(annotatedMethod.getExceptionTypes(), method.getExceptionTypes()))
                            return invocation.invoke();
                    }
                }
                clazz = clazz.getSuperclass();
            }
        }

        return super.doIntercept(invocation);
    }

    // FIXME: This is copied from DefaultActionInvocation but should be exposed through the interface
    protected Method getActionMethod(Class actionClass, String methodName) throws NoSuchMethodException {
        Method method = null;
        try {
            method = actionClass.getMethod(methodName, new Class[0]);
        } catch (NoSuchMethodException e) {
            // hmm -- OK, try doXxx instead
            try {
                String altMethodName = "do" + methodName.substring(0, 1).toUpperCase() + methodName.substring(1);
                method = actionClass.getMethod(altMethodName, new Class[0]);
            } catch (NoSuchMethodException e1) {
                // throw the original one
                if (devMode) {
                    throw e;
                }
            }
        }
        return method;
    }

}
View Code

2、validation拦截器调用父类拦截器com.opensymphony.xwork2.validator.ValidationInterceptor

/*
 * Copyright 2002-2007,2009 The Apache Software Foundation.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.opensymphony.xwork2.validator;

import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.ActionProxy;
import com.opensymphony.xwork2.Validateable;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.interceptor.MethodFilterInterceptor;
import com.opensymphony.xwork2.interceptor.PrefixMethodInvocationUtil;
import com.opensymphony.xwork2.util.logging.Logger;
import com.opensymphony.xwork2.util.logging.LoggerFactory;

/**
 * <!-- START SNIPPET: description -->
 *
 * This interceptor runs the action through the standard validation framework, which in turn checks the action against
 * any validation rules (found in files such as <i>ActionClass-validation.xml</i>) and adds field-level and action-level
 * error messages (provided that the action implements {@link com.opensymphony.xwork2.ValidationAware}). This interceptor
 * is often one of the last (or second to last) interceptors applied in a stack, as it assumes that all values have
 * already been set on the action.
 *
 * <p/>This interceptor does nothing if the name of the method being invoked is specified in the <b>excludeMethods</b>
 * parameter. <b>excludeMethods</b> accepts a comma-delimited list of method names. For example, requests to
 * <b>foo!input.action</b> and <b>foo!back.action</b> will be skipped by this interceptor if you set the
 * <b>excludeMethods</b> parameter to "input, back".
 * 
 * </ol>
 * 
 * <p/> The workflow of the action request does not change due to this interceptor. Rather,
 * this interceptor is often used in conjuction with the <b>workflow</b> interceptor.
 *
 * <p/>
 * 
 * <b>NOTE:</b> As this method extends off MethodFilterInterceptor, it is capable of
 * deciding if it is applicable only to selective methods in the action class. See
 * <code>MethodFilterInterceptor</code> for more info.
 *
 * <!-- END SNIPPET: description -->
 *
 * <p/> <u>Interceptor parameters:</u>
 *
 * <!-- START SNIPPET: parameters -->
 *
 * <ul>
 *
 * <li>alwaysInvokeValidate - Defaults to true. If true validate() method will always
 * be invoked, otherwise it will not.</li>
 *
 * <li>programmatic - Defaults to true. If true and the action is Validateable call validate(),
 * and any method that starts with "validate".
 * </li>
 * 
 * <li>declarative - Defaults to true. Perform validation based on xml or annotations.</li>
 * 
 * </ul>
 *
 * <!-- END SNIPPET: parameters -->
 *
 * <p/> <u>Extending the interceptor:</u>
 *
 * <p/>
 *
 * <!-- START SNIPPET: extending -->
 *
 * There are no known extension points for this interceptor.
 *
 * <!-- END SNIPPET: extending -->
 *
 * <p/> <u>Example code:</u>
 *
 * <pre>
 * <!-- START SNIPPET: example -->
 * 
 * &lt;action name="someAction" class="com.examples.SomeAction"&gt;
 *     &lt;interceptor-ref name="params"/&gt;
 *     &lt;interceptor-ref name="validation"/&gt;
 *     &lt;interceptor-ref name="workflow"/&gt;
 *     &lt;result name="success"&gt;good_result.ftl&lt;/result&gt;
 * &lt;/action&gt;
 * 
 * &lt;-- in the following case myMethod of the action class will not
 *        get validated --&gt;
 * &lt;action name="someAction" class="com.examples.SomeAction"&gt;
 *     &lt;interceptor-ref name="params"/&gt;
 *     &lt;interceptor-ref name="validation"&gt;
 *         &lt;param name="excludeMethods"&gt;myMethod&lt;/param&gt;
 *     &lt;/interceptor-ref&gt;
 *     &lt;interceptor-ref name="workflow"/&gt;
 *     &lt;result name="success"&gt;good_result.ftl&lt;/result&gt;
 * &lt;/action&gt;
 * 
 * &lt;-- in the following case only annotated methods of the action class will
 *        be validated --&gt;
 * &lt;action name="someAction" class="com.examples.SomeAction"&gt;
 *     &lt;interceptor-ref name="params"/&gt;
 *     &lt;interceptor-ref name="validation"&gt;
 *         &lt;param name="validateAnnotatedMethodOnly"&gt;true&lt;/param&gt;
 *     &lt;/interceptor-ref&gt;
 *     &lt;interceptor-ref name="workflow"/&gt;
 *     &lt;result name="success"&gt;good_result.ftl&lt;/result&gt;
 * &lt;/action&gt;
 *
 *
 * <!-- END SNIPPET: example -->
 * </pre>
 *
 * @author Jason Carreira
 * @author Rainer Hermanns
 * @author <a href='mailto:the_mindstorm[at]evolva[dot]ro'>Alexandru Popescu</a>
 * @see ActionValidatorManager
 * @see com.opensymphony.xwork2.interceptor.DefaultWorkflowInterceptor
 */
public class ValidationInterceptor extends MethodFilterInterceptor {

    private boolean validateAnnotatedMethodOnly;
    
    private ActionValidatorManager actionValidatorManager;
    
    private static final Logger LOG = LoggerFactory.getLogger(ValidationInterceptor.class);
    
    private final static String VALIDATE_PREFIX = "validate";
    private final static String ALT_VALIDATE_PREFIX = "validateDo";
    
    private boolean alwaysInvokeValidate = true;
    private boolean programmatic = true;
    private boolean declarative = true;

    @Inject
    public void setActionValidatorManager(ActionValidatorManager mgr) {
        this.actionValidatorManager = mgr;
    }
    
    /**
     * Determines if {@link Validateable}'s <code>validate()</code> should be called,
     * as well as methods whose name that start with "validate". Defaults to "true".
     * 
     * @param programmatic <tt>true</tt> then <code>validate()</code> is invoked.
     */
    public void setProgrammatic(boolean programmatic) {
        this.programmatic = programmatic;
    }

    /**
     * Determines if validation based on annotations or xml should be performed. Defaults 
     * to "true".
     * 
     * @param declarative <tt>true</tt> then perform validation based on annotations or xml.
     */
    public void setDeclarative(boolean declarative) {
        this.declarative = declarative;
    }

    /**
     * Determines if {@link Validateable}'s <code>validate()</code> should always 
     * be invoked. Default to "true".
     * 
     * @param alwaysInvokeValidate <tt>true</tt> then <code>validate()</code> is always invoked.
     */
    public void setAlwaysInvokeValidate(String alwaysInvokeValidate) {
            this.alwaysInvokeValidate = Boolean.parseBoolean(alwaysInvokeValidate);
    }

    /**
     * Gets if <code>validate()</code> should always be called or only per annotated method.
     *
     * @return <tt>true</tt> to only validate per annotated method, otherwise <tt>false</tt> to always validate.
     */
    public boolean isValidateAnnotatedMethodOnly() {
        return validateAnnotatedMethodOnly;
    }

    /**
     * Determine if <code>validate()</code> should always be called or only per annotated method.
     * Default to <tt>false</tt>.
     *
     * @param validateAnnotatedMethodOnly  <tt>true</tt> to only validate per annotated method, otherwise <tt>false</tt> to always validate.
     */
    public void setValidateAnnotatedMethodOnly(boolean validateAnnotatedMethodOnly) {
        this.validateAnnotatedMethodOnly = validateAnnotatedMethodOnly;
    }

    /**
     * Gets the current action and its context and delegates to {@link ActionValidatorManager} proper validate method.
     *
     * @param invocation  the execution state of the Action.
     * @throws Exception if an error occurs validating the 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, see WW-3194
        //UPDATE:  see WW-3753
        String context = this.getValidationContext(proxy);
        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;
            }
        }
    }

    @Override
    protected String doIntercept(ActionInvocation invocation) throws Exception {
        doBeforeInvocation(invocation);
        
        return invocation.invoke();
    }
    
    /**
     * Returns the context that will be used by the
     * {@link ActionValidatorManager} to associate the action invocation with
     * the appropriate {@link ValidatorConfig ValidatorConfigs}.
     * <p>
     * The context returned is used in the pattern
     * <i>ActionClass-context-validation.xml</i>
     * <p>
     * The default context is the action name from the URL, but the method can
     * be overridden to implement custom contexts.
     * <p>
     * This can be useful in cases in which a single action and a single model
     * require vastly different validation based on some condition.
     * 
     * @return the Context
     */
    protected String getValidationContext(ActionProxy proxy) {
        // This method created for WW-3753
        return proxy.getActionName();
    }

}
View Code

3、在validation拦截器父类拦截器中的doIntercept()方法中,调用了doBeforeInvocation()方法,在该方法中:

       if (declarative) {
           if (validateAnnotatedMethodOnly) {
               actionValidatorManager.validate(action, context, method);
           } else {
               actionValidatorManager.validate(action, context);
           }
       }    

的actionValidatorManager对象就是com.opensymphony.xwork2.validator.AnnotationActionValidatorManager

/*
 * Copyright 2002-2006,2009 The Apache Software Foundation.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.opensymphony.xwork2.validator;


import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.ActionProxy;
import com.opensymphony.xwork2.FileManager;
import com.opensymphony.xwork2.FileManagerFactory;
import com.opensymphony.xwork2.XWorkConstants;
import com.opensymphony.xwork2.config.entities.ActionConfig;
import com.opensymphony.xwork2.inject.Inject;
import com.opensymphony.xwork2.util.ClassLoaderUtil;
import com.opensymphony.xwork2.util.ValueStack;
import com.opensymphony.xwork2.util.logging.Logger;
import com.opensymphony.xwork2.util.logging.LoggerFactory;
import org.apache.commons.lang3.StringUtils;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

/**
 * AnnotationActionValidatorManager is the entry point into XWork's annotations-based validator framework.
 * Validation rules are specified as annotations within the source files.
 *
 * @author Rainer Hermanns
 * @author jepjep
 */
public class AnnotationActionValidatorManager implements ActionValidatorManager {

    /**
     * The file suffix for any validation file.
     */
    protected static final String VALIDATION_CONFIG_SUFFIX = "-validation.xml";

    private final Map<String, List<ValidatorConfig>> validatorCache = Collections.synchronizedMap(new HashMap<String, List<ValidatorConfig>>());
    private final Map<String, List<ValidatorConfig>> validatorFileCache = Collections.synchronizedMap(new HashMap<String, List<ValidatorConfig>>());
    private static final Logger LOG = LoggerFactory.getLogger(AnnotationActionValidatorManager.class);

    private ValidatorFactory validatorFactory;
    private ValidatorFileParser validatorFileParser;
    private FileManager fileManager;
    private boolean reloadingConfigs;

    @Inject
    public void setValidatorFactory(ValidatorFactory fac) {
        this.validatorFactory = fac;
    }

    @Inject
    public void setValidatorFileParser(ValidatorFileParser parser) {
        this.validatorFileParser = parser;
    }

    @Inject
    public void setFileManagerFactory(FileManagerFactory fileManagerFactory) {
        this.fileManager = fileManagerFactory.getFileManager();
    }

    @Inject(value = XWorkConstants.RELOAD_XML_CONFIGURATION, required = false)
    public void setReloadingConfigs(String reloadingConfigs) {
        this.reloadingConfigs = Boolean.parseBoolean(reloadingConfigs);
    }

    public List<Validator> getValidators(Class clazz, String context) {
        return getValidators(clazz, context, null);
    }

    public List<Validator> getValidators(Class clazz, String context, String method) {
        final String validatorKey = buildValidatorKey(clazz, context);
        final List<ValidatorConfig> cfgs;

        if (validatorCache.containsKey(validatorKey)) {
            if (reloadingConfigs) {
                validatorCache.put(validatorKey, buildValidatorConfigs(clazz, context, true, null));
            }
        } else {
            validatorCache.put(validatorKey, buildValidatorConfigs(clazz, context, false, null));
        }

        // get the set of validator configs
        cfgs = new ArrayList<ValidatorConfig>(validatorCache.get(validatorKey));

        ValueStack stack = ActionContext.getContext().getValueStack();

        // create clean instances of the validators for the caller's use
        ArrayList<Validator> validators = new ArrayList<Validator>(cfgs.size());
        for (ValidatorConfig cfg : cfgs) {
            if (method == null || method.equals(cfg.getParams().get("methodName"))) {
                Validator validator = validatorFactory.getValidator(
                        new ValidatorConfig.Builder(cfg)
                                .removeParam("methodName")
                                .build());
                validator.setValidatorType(cfg.getType());
                validator.setValueStack(stack);
                validators.add(validator);
            }
        }

        return validators;
    }

    public void validate(Object object, String context) throws ValidationException {
        validate(object, context, (String) null);
    }

    public void validate(Object object, String context, String method) throws ValidationException {
        ValidatorContext validatorContext = new DelegatingValidatorContext(object);
        validate(object, context, validatorContext, method);
    }

    public void validate(Object object, String context, ValidatorContext validatorContext) throws ValidationException {
        validate(object, context, validatorContext, null);
    }

    public void validate(Object object, String context, ValidatorContext validatorContext, String method) throws ValidationException {
        List<Validator> validators = getValidators(object.getClass(), context, method);
        Set<String> shortcircuitedFields = null;

        for (final Validator validator : validators) {
            try {
                validator.setValidatorContext(validatorContext);

                if (LOG.isDebugEnabled()) {
                    LOG.debug("Running validator: " + validator + " for object " + object + " and method " + method);
                }

                FieldValidator fValidator = null;
                String fullFieldName = null;

                if (validator instanceof FieldValidator) {
                    fValidator = (FieldValidator) validator;
                    fullFieldName = fValidator.getValidatorContext().getFullFieldName(fValidator.getFieldName());

                    if ((shortcircuitedFields != null) && shortcircuitedFields.contains(fullFieldName)) {
                        if (LOG.isDebugEnabled()) {
                            LOG.debug("Short-circuited, skipping");
                        }

                        continue;
                    }
                }

                if (validator instanceof ShortCircuitableValidator && ((ShortCircuitableValidator) validator).isShortCircuit()) {
                    // get number of existing errors
                    List<String> errs = null;

                    if (fValidator != null) {
                        if (validatorContext.hasFieldErrors()) {
                            Collection<String> fieldErrors = validatorContext.getFieldErrors().get(fullFieldName);

                            if (fieldErrors != null) {
                                errs = new ArrayList<String>(fieldErrors);
                            }
                        }
                    } else if (validatorContext.hasActionErrors()) {
                        Collection<String> actionErrors = validatorContext.getActionErrors();

                        if (actionErrors != null) {
                            errs = new ArrayList<String>(actionErrors);
                        }
                    }

                    validator.validate(object);

                    if (fValidator != null) {
                        if (validatorContext.hasFieldErrors()) {
                            Collection<String> errCol = validatorContext.getFieldErrors().get(fullFieldName);

                            if ((errCol != null) && !errCol.equals(errs)) {
                                if (LOG.isDebugEnabled()) {
                                    LOG.debug("Short-circuiting on field validation");
                                }

                                if (shortcircuitedFields == null) {
                                    shortcircuitedFields = new TreeSet<String>();
                                }

                                shortcircuitedFields.add(fullFieldName);
                            }
                        }
                    } else if (validatorContext.hasActionErrors()) {
                        Collection<String> errCol = validatorContext.getActionErrors();

                        if ((errCol != null) && !errCol.equals(errs)) {
                            if (LOG.isDebugEnabled()) {
                                LOG.debug("Short-circuiting");
                            }

                            break;
                        }
                    }

                    continue;
                }

                validator.validate(object);
            } finally {
                validator.setValidatorContext(null);
            }

        }
    }

    /**
     * Builds a key for validators - used when caching validators.
     *
     * @param clazz the action.
     * @return a validator key which is the class name plus context.
     */
    protected String buildValidatorKey(Class clazz, String context) {
        ActionInvocation invocation = ActionContext.getContext().getActionInvocation();
        ActionProxy proxy = invocation.getProxy();
        ActionConfig config = proxy.getConfig();

        StringBuilder sb = new StringBuilder(clazz.getName());
        sb.append("/");
        if (StringUtils.isNotBlank(config.getPackageName())) {
            sb.append(config.getPackageName());
            sb.append("/");
        }

        // the key needs to use the name of the action from the config file,
        // instead of the url, so wild card actions will have the same validator
        // see WW-2996

        // UPDATE:
        // WW-3753 Using the config name instead of the context only for
        // wild card actions to keep the flexibility provided
        // by the original design (such as mapping different contexts
        // to the same action and method if desired)

        // UPDATE:
        // WW-4536 Using NameVariablePatternMatcher allows defines actions
        // with patterns enclosed with '{}', it's similar case to WW-3753
        String configName = config.getName();
        if (configName.contains(ActionConfig.WILDCARD) || (configName.contains("{") && configName.contains("}"))) {
            sb.append(configName);
            sb.append("|");
            sb.append(proxy.getMethod());
        } else {
            sb.append(context);
        }
        
        return sb.toString();
    }

    private List<ValidatorConfig> buildAliasValidatorConfigs(Class aClass, String context, boolean checkFile) {
        String fileName = aClass.getName().replace('.', '/') + "-" + context.replace('/', '-') + VALIDATION_CONFIG_SUFFIX;

        return loadFile(fileName, aClass, checkFile);
    }


    protected List<ValidatorConfig> buildClassValidatorConfigs(Class aClass, boolean checkFile) {

        String fileName = aClass.getName().replace('.', '/') + VALIDATION_CONFIG_SUFFIX;

        List<ValidatorConfig> result = new ArrayList<ValidatorConfig>(loadFile(fileName, aClass, checkFile));

        AnnotationValidationConfigurationBuilder builder = new AnnotationValidationConfigurationBuilder(validatorFactory);

        List<ValidatorConfig> annotationResult = new ArrayList<ValidatorConfig>(builder.buildAnnotationClassValidatorConfigs(aClass));

        result.addAll(annotationResult);

        return result;

    }

    /**
     * <p>This method 'collects' all the validator configurations for a given
     * action invocation.</p>
     * <p/>
     * <p>It will traverse up the class hierarchy looking for validators for every super class
     * and directly implemented interface of the current action, as well as adding validators for
     * any alias of this invocation. Nifty!</p>
     * <p/>
     * <p>Given the following class structure:
     * <pre>
     *   interface Thing;
     *   interface Animal extends Thing;
     *   interface Quadraped extends Animal;
     *   class AnimalImpl implements Animal;
     *   class QuadrapedImpl extends AnimalImpl implements Quadraped;
     *   class Dog extends QuadrapedImpl;
     * </pre></p>
     * <p/>
     * <p>This method will look for the following config files for Dog:
     * <pre>
     *   Animal
     *   Animal-context
     *   AnimalImpl
     *   AnimalImpl-context
     *   Quadraped
     *   Quadraped-context
     *   QuadrapedImpl
     *   QuadrapedImpl-context
     *   Dog
     *   Dog-context
     * </pre></p>
     * <p/>
     * <p>Note that the validation rules for Thing is never looked for because no class in the
     * hierarchy directly implements Thing.</p>
     *
     * @param clazz     the Class to look up validators for.
     * @param context   the context to use when looking up validators.
     * @param checkFile true if the validation config file should be checked to see if it has been
     *                  updated.
     * @param checked   the set of previously checked class-contexts, null if none have been checked
     * @return a list of validator configs for the given class and context.
     */
    private List<ValidatorConfig> buildValidatorConfigs(Class clazz, String context, boolean checkFile, Set<String> checked) {
        List<ValidatorConfig> validatorConfigs = new ArrayList<ValidatorConfig>();

        if (checked == null) {
            checked = new TreeSet<String>();
        } else if (checked.contains(clazz.getName())) {
            return validatorConfigs;
        }

        if (clazz.isInterface()) {
            Class[] interfaces = clazz.getInterfaces();

            for (Class anInterface : interfaces) {
                validatorConfigs.addAll(buildValidatorConfigs(anInterface, context, checkFile, checked));
            }
        } else {
            if (!clazz.equals(Object.class)) {
                validatorConfigs.addAll(buildValidatorConfigs(clazz.getSuperclass(), context, checkFile, checked));
            }
        }

        // look for validators for implemented interfaces
        Class[] interfaces = clazz.getInterfaces();

        for (Class anInterface1 : interfaces) {
            if (checked.contains(anInterface1.getName())) {
                continue;
            }

            validatorConfigs.addAll(buildClassValidatorConfigs(anInterface1, checkFile));

            if (context != null) {
                validatorConfigs.addAll(buildAliasValidatorConfigs(anInterface1, context, checkFile));
            }

            checked.add(anInterface1.getName());
        }

        validatorConfigs.addAll(buildClassValidatorConfigs(clazz, checkFile));

        if (context != null) {
            validatorConfigs.addAll(buildAliasValidatorConfigs(clazz, context, checkFile));
        }

        checked.add(clazz.getName());

        return validatorConfigs;
    }

    private List<ValidatorConfig> loadFile(String fileName, Class clazz, boolean checkFile) {
        List<ValidatorConfig> retList = Collections.emptyList();

        URL fileUrl = ClassLoaderUtil.getResource(fileName, clazz);

        if ((checkFile && fileManager.fileNeedsReloading(fileUrl)) || !validatorFileCache.containsKey(fileName)) {
            InputStream is = null;

            try {
                is = fileManager.loadFile(fileUrl);

                if (is != null) {
                    retList = new ArrayList<ValidatorConfig>(validatorFileParser.parseActionValidatorConfigs(validatorFactory, is, fileName));
                }
            } catch (Exception e) {
                LOG.error("Caught exception while loading file " + fileName, e);
            } finally {
                if (is != null) {
                    try {
                        is.close();
                    } catch (IOException e) {
                        LOG.error("Unable to close input stream for " + fileName, e);
                    }
                }
            }

            validatorFileCache.put(fileName, retList);
        } else {
            retList = validatorFileCache.get(fileName);
        }

        return retList;
    }
}
View Code

(查看方式:续重validate方法,ctrl+T)。

4、com.opensymphony.xwork2.validator.AnnotationActionValidatorManager该类中的方法:

 public void validate(Object object, String context, ValidatorContext validatorContext, String method) throws ValidationException {
        List<Validator> validators = getValidators(object.getClass(), context, method);
        Set<String> shortcircuitedFields = null;

        for (final Validator validator : validators) {
            try {
                validator.setValidatorContext(validatorContext);

               。。。

                validator.validate(object);
            } finally {
                validator.setValidatorContext(null);
            }

        }
    }

中的validator.validate(object);代码,将会调用com.opensymphony.xwork2.validator.validators.IntRangeFieldValidator.

流程图:

 
其中validation配置在xwrok-core.jar中的com.opensymphony.xwork2.validator.validators包下的default.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>
<!--  END SNIPPET: validators-default -->

 

 
 

 

posted @ 2017-04-09 19:23  cctext  阅读(605)  评论(0编辑  收藏  举报