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>
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 --> * * <action name="someAction" class="com.examples.SomeAction"> * <interceptor-ref name="params"/> * <interceptor-ref name="validation"/> * <interceptor-ref name="workflow"/> * <result name="success">good_result.ftl</result> * </action> * * <-- in the following case myMethod of the action class will not * get validated --> * <action name="someAction" class="com.examples.SomeAction"> * <interceptor-ref name="params"/> * <interceptor-ref name="validation"> * <param name="excludeMethods">myMethod</param> * </interceptor-ref> * <interceptor-ref name="workflow"/> * <result name="success">good_result.ftl</result> * </action> * * <-- in the following case only annotated methods of the action class will * be validated --> * <action name="someAction" class="com.examples.SomeAction"> * <interceptor-ref name="params"/> * <interceptor-ref name="validation"> * <param name="validateAnnotatedMethodOnly">true</param> * </interceptor-ref> * <interceptor-ref name="workflow"/> * <result name="success">good_result.ftl</result> * </action> * * * <!-- 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+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.
流程图:
<?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 -->
基础才是编程人员应该深入研究的问题,比如:
1)List/Set/Map内部组成原理|区别
2)mysql索引存储结构&如何调优/b-tree特点、计算复杂度及影响复杂度的因素。。。
3)JVM运行组成与原理及调优
4)Java类加载器运行原理
5)Java中GC过程原理|使用的回收算法原理
6)Redis中hash一致性实现及与hash其他区别
7)Java多线程、线程池开发、管理Lock与Synchroined区别
8)Spring IOC/AOP 原理;加载过程的。。。
【+加关注】。