AnnotationValidationInterceptor
AnnotationValidationInterceptor拦截器处于defaultStack第十七的位置,主要是用于数据校验的,该拦截器继承自ValidationInterceptor拦截器增加了在方法上使用注解取消校验功能。ValidationInterceptor又继承自MethodFilterInterceptor。前面继承自MethodFilterInterceptor的拦截器中都没有讲MethodFilterInterceptor,在AnnotationValidationInterceptor配置中传递了一个名为excludeMethods的参数,这个参数就是提交到MethodFilterInterceptor中的,用于指定哪些方法是不需要进行校验的。所以这里先讲解MethodFilterInterceptor拦截器,下面是MethodFilterInterceptor的intercept源码:
@Override
public String intercept(ActionInvocation invocation) throws Exception {
if (applyInterceptor(invocation)) {
return doIntercept(invocation);
}
return invocation.invoke();
}
这里在执行invocation.invoke();之前调用了applyInterceptor判断是否要应用上该拦截器,下面看一下applyInterceptor方法:
protected boolean applyInterceptor(ActionInvocation invocation) {
String method = invocation.getProxy().getMethod();
//真正判断的方法是MethodFilterInterceptorUtil.applyMethod方法,把排除的方法集合与包含的方法集合与Action要执行的方法名传入
//该方法把字符串转成了正则表达式对该方法进行匹配,逻辑不难,但判断代码比较多,所以讲到这吧...
boolean applyMethod = MethodFilterInterceptorUtil.applyMethod(excludeMethods, includeMethods, method);
if (log.isDebugEnabled()) {
if (!applyMethod) {
log.debug("Skipping Interceptor... Method [" + method + "] found in exclude list.");
}
}
return applyMethod;
}
所以所有继承自MethodFilterInterceptor的拦截器都可以设置excludeMethods与includeMethods参数用于指定哪些方法要应用上该拦截器,哪些方法不需要应用上该拦截器,对于AnnotationValidationInterceptor就是哪些方法要进行校验与哪方法不需要进行校验。AnnotationValidationInterceptor在defaultStack中的配置为:
<interceptor-ref name="validation">
<param name="excludeMethods">input,back,cancel,browse</param>
</interceptor-ref>
即排除input,back,cancel,browse这四个方法外,其它执行的Action方法都要进行校验。
现在假设要进行校验,所以会执行AnnotationValidationInterceptor的doIntercept方法,下面是该方法源码:
protected String doIntercept(ActionInvocation invocation) throws Exception {
//获取当前执行的Action
Object action = invocation.getAction();
if (action != null) {//如果Action不为null
Method method = getActionMethod(action.getClass(), invocation.getProxy().getMethod());//获取Action要执行的方法
//获取Action中加了SkipValidation注解的方法集合
Collection<Method> annotatedMethods = AnnotationUtils.getAnnotatedMethods(action.getClass(), SkipValidation.class);
if (annotatedMethods.contains(method))
return invocation.invoke();//如果当前执行的方法有SkipValidation注解则不进行校验,调用下一个拦截器
//检测是否有覆盖父类标有SkipValidation注解的方法
Class clazz = action.getClass().getSuperclass();//获取父类字节码
while (clazz != null) {
annotatedMethods = AnnotationUtils.getAnnotatedMethods(clazz, SkipValidation.class);//获取父类标有SkipValidation注解的方法集合
if (annotatedMethods != null) {//如果方法不为null
//如果当前要执行的方法是覆盖了父类的方法,而父类方法标有SkipValidation注解,则当前方法也不进行校验
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();//获取父类字节码
}
}
//如果要进行校验,继续调用父类的doIntercept方法
return super.doIntercept(invocation);
}
从上面可以看到如果当前Action执行的方法上面如果标注有SkipValidation注解或者其覆盖的方法上标注有SkipValidation注解就不会对该方法进行校验,执行完成后还调用了父类ValidationInterceptor的doIntercept方法,下面该方法源码:
@Override
protected String doIntercept(ActionInvocation invocation) throws Exception {
doBeforeInvocation(invocation);//调用doBeforeInvocation方法
return invocation.invoke();//调用下一个拦截器
}
doBeforeInvocation(invocation)方法源码:
protected void doBeforeInvocation(ActionInvocation invocation) throws Exception {
Object action = invocation.getAction();//获取当前执行的Action
ActionProxy proxy = invocation.getProxy();//获取ActionProxy对象
//the action name has to be from the url, otherwise validators that use aliases, like
//MyActio-someaction-validator.xml will not be found, see WW-3194
String context = proxy.getActionName();//获取Action名称
String method = proxy.getMethod();//获取执行Action的方法名称
//省略...
//declarative默认为true
if (declarative) {
if (validateAnnotatedMethodOnly) {//validateAnnotatedMethodOnly默认为false
actionValidatorManager.validate(action, context, method);
} else {
actionValidatorManager.validate(action, context);//所以执行这里
}
}
//如果Action实现了Validateable接口,ActionSupport实现了Validateable接口
if (action instanceof Validateable && programmatic) {//programmatic默认为true
Exception exception = null;
//强转
Validateable validateable = (Validateable) action;
if (LOG.isDebugEnabled()) {
LOG.debug("Invoking validate() on action "+validateable);
}
try {//调用有validate,validateDo前缀的方法
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
LOG.warn("an exception occured while executing the prefix method", e);
exception = e;
}
//alwaysInvokeValidate默认为true,总是调用Action的validate方法
if (alwaysInvokeValidate) {
validateable.validate();
}
if (exception != null) {
// rethrow if something is wrong while doing validateXXX / validateDoXXX
throw exception;
}
}
}
因为struts2提供了声明式校验的功能,即使用XML文件对提交过来的数据进行校验,而这种声明式校验就是由actionValidatorManager.validate(action, context);这句代码实现的调用ActionValidatorManager的validate方法,其方法内部就是去查找相应的XML校验文件,解析XML校验文件生成com.opensymphony.xwork2.validator.Validator检验器对象,然后对象提交的数据进行校验,如果校验有些数据不合法则会将相应的错误信息通过addFieldError添加字段错误信息打印到控制台。因为声明式校验功能涉及寻找XML校验文件,解析XML校验文件生成校验器对象,再使用校验器对象进行校验数据,里面还添加了校验器缓存,所以里面的代码量很大,在这只能
讲大概原理,具体细节有兴趣可以自己去研究。
如果Action继承自ActionSupport类(通常),则实现了Validateable接口,接下来就会调用带有validate或validateDo前缀的校验方法,就是通过PrefixMethodInvocationUtil这个工具类调用的,这个工具类前面我们已经遇到过了,在讲解PrepareInterceptor拦截器的时候,会调用带有prepare或prepareDo前缀的方法。带有validate或validateDo前缀的校验方法如果同时存在的话只会执行带有validate前缀的方法,这是PrefixMethodInvocationUtil
这个工具类内部代码决定的。ValidationInterceptor的alwaysInvokeValidate属性默认为true,所以Action的validate方法总是会调用的,即validateable.validate();这句代码会执行。在validate方法中使用代码进行校验。
到这里doBeforeInvocation方法就执行完成了,整个AnnotationValidationInterceptor逻辑也就执行完成,最后调用invocation.invoke();调用下一个拦截器......