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

时间:2025-01-27 13:07:32
  • 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

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

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

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

  编写验证规则,参考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,提交:

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

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>

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>
  • 运行原理分析:

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;
} }

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();
} }

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;
}
}

(查看方式:续重validate方法,ctrl+TStruts(二十三):使用声名式验证)。

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.

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

其中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 -->