Struts2拦截器之DefaultWorkflowInterceptor
一、DefaultWorkflowInterceptor是什么
首先说这东西是干嘛来的,在action中可以对传进来的数据进行验证,方法是实现Validateable接口的validate():void方法,如果发现错误可以addFieldError()或者addActionError(),当添加了错误之后struts2就不会再执行原本的映射方法,而是会直接返回一个结果字符串(如果正常的话返回的应该是要执行的映射到方法的返回值),我们这里要探讨的就是struts2是怎么知道我们添加了错误,又是怎么返回一个什么样的结果字符串呢?
二、DefaultWorkflowInterceptor源码分析
DefaultWorkflowInterceptor处于默认拦截器栈的最后(即第18个),主要是用来判断一下当前的action方法在执行过程中是否出现了错误,如果出现错误的话就返回一个表示错误的字符串同时打断拦截器执行栈。
下图是struts-default.xml文件中的默认拦截器栈这个类的配置信息:
需要注意的是对execludeMethods中指定的方法不执行DefaultWorkflowInterceptor拦截器。
DefaultWorkflowInterceptor的代码剖析:
网页看对齐、高亮效果不太好,建议粘贴到eclipse慢慢看 :-)
1 /**
2 * DefaultWorkflowInterceptor:
3 * 处于默认的拦截器栈最后的位置,用来判断Action的执行过程中有无错误
4 *
5 * **/
6 public class DefaultWorkflowInterceptor extends MethodFilterInterceptor {
7
8 private static final long serialVersionUID = 7563014655616490865L;
9
10 //日志
11 private static final Logger LOG = LoggerFactory.getLogger(DefaultWorkflowInterceptor.class);
12
13 //酱油位,大概是为了避免每次都创建的话会很浪费吧(注意是static的哦)
14 private static final Class[] EMPTY_CLASS_ARRAY = new Class[0];
15
16 //表示要求重新输入的字符,当检查出问题的时候就返回这个字符串让用户重新输入
17 private String inputResultName = Action.INPUT;
18
19 /**
20 * Set the <code>inputResultName</code> (result name to be returned when
21 * a action / field error is found registered). Default to {@link Action#INPUT}
22 *
23 * @param inputResultName what result name to use when there was validation error(s).
24 */ //在配置的时候可以传入一个inputResultName的param,会通过这个方法设置进来的
25 public void setInputResultName(String inputResultName) {
26 this.inputResultName = inputResultName;
27 }
28
29 /**
30 * Intercept {@link ActionInvocation} and returns a <code>inputResultName</code>
31 * when action / field errors is found registered.
32 *
33 * @return String result name
34 */
35 @Override
36 protected String doIntercept(ActionInvocation invocation) throws Exception {
37
38 //先获得当前的action对象
39 Object action = invocation.getAction();
40
41 //判断这个action是否实现了ValidationAware接口,意思就是看看当前的action需不需要验证
42 if (action instanceof ValidationAware) {
43
44 //首先进行类型转换,类型转换后的action就会被当做ValidationAware处理了,下面就称这个action对象为ValidationAware实例
45 ValidationAware validationAwareAction = (ValidationAware) action;
46
47 //如果这个ValidationAware实例中已经包含了错误信息,就进行一些操作然后把拦截器执行栈咔嚓掉,然后直接返回特定字串(默认是Action.INPUT)
48 if (validationAwareAction.hasErrors()) {
49
50 //是否记录日志
51 if (LOG.isDebugEnabled()) {
52 LOG.debug("Errors on action " + validationAwareAction + ", returning result name 'input'");
53 }
54
55 //默认的返回字串为Action.INPUT,是写死的,不过没关系我们后面可以通过实现ValidationWorkflowAware接口来自定义
56 String resultName = inputResultName;
57
58 /*Action可以实现ValidationWorkflowAware然后实现getInputResultName():String方法来自定义当发生错误的时候的返回字串
59 比如实现不同的方法验证出处返回不同的字串,只需要在检验出错的时候设定一个成员变量的值然后在getInputResultName()返回其值就可以了,比如
60 在create方法中出错将事先声明的成员变量inputResultName设定为“create”,而在delete中出错将inputResultName设定为“delete”,这样就可以区分它们并可以跳转到不同的页面啦
61 但这只是一种折中的办法,一个良好的规范是ValidationWorkflowAware提供了方法级别的自定义返回字串,而@InputConfig提供了方法级别的自定义返回字串
62 */
63 if (action instanceof ValidationWorkflowAware) {
64 resultName = ((ValidationWorkflowAware) action).getInputResultName();
65 }
66
67 //获取执行的action方法上的@InputConfig注解
68 InputConfig annotation = action.getClass().getMethod(invocation.getProxy().getMethod(), EMPTY_CLASS_ARRAY).getAnnotation(InputConfig.class);
69 //如果确实加了这个注解的话
70 if (annotation != null) {
71
72 if (!annotation.methodName().equals("")) {
73 //调用注解上配置的methodName指定的方法,执行这个方法,使用这个方法的返回值作为返回字串
74 Method method = action.getClass().getMethod(annotation.methodName());
75 //使用这个方法的返回值作为返回字串,这个方法可以做一些事情动态生成返回字串
76 resultName = (String) method.invoke(action);
77 } else {
78 //或者使用resultName作为返回字符串 ,这个值是直接指定的一个字符串
79 resultName = annotation.resultName();
80 }
81 }
82
83 //悲剧了,拦截器栈不往下走了,到此结束掉(虽然在默认的拦截器栈里这就是最后一个了)
84 return resultName;
85 }
86 }
87
88 //拦截器栈继续走
89 return invocation.invoke();
90 }
91
92 }
字太多不看?呐呐,有图有真相:
总结一下:
这个拦截器是用来判断当前action的执行是否发生了错误以及发生错误时应该返回什么字串结果。
三、自定义返回结果字符串
返回的字串结果默认为Action.INPUT,允许自定义,自定义的办法有这么几种
1. 在配置拦截器的时候传入一个inputResultName的参数,这种是全局级别的,对所有的action都生效。
实现方式:修改配置文件
拷贝struts-default.xml的defaultStack拦截栈到我们自己的配置文件中,如下:
1 <?xml version="1.0" encoding="UTF-8" ?> 2 <!DOCTYPE struts PUBLIC 3 "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN" 4 "http://struts.apache.org/dtds/struts-2.0.dtd"> 5 6 <struts> 7 8 <package name="default" namespace="/" extends="struts-default" 9 abstract="false"> 10 11 <interceptors> 12 <interceptor-stack name="defaultStack"> 13 <interceptor-ref name="exception" /> 14 <interceptor-ref name="alias" /> 15 <interceptor-ref name="servletConfig" /> 16 <interceptor-ref name="i18n" /> 17 <interceptor-ref name="prepare" /> 18 <interceptor-ref name="chain" /> 19 <interceptor-ref name="debugging" /> 20 <interceptor-ref name="scopedModelDriven" /> 21 <interceptor-ref name="modelDriven" /> 22 <interceptor-ref name="fileUpload" /> 23 <interceptor-ref name="checkbox" /> 24 <interceptor-ref name="multiselect" /> 25 <interceptor-ref name="staticParams" /> 26 <interceptor-ref name="actionMappingParams" /> 27 28 <!-- 参数拦截器,就是这个拦截器将前台传过来的参数设置到ValueStack中去 --> 29 <interceptor-ref name="params"> 30 <param name="excludeParams">dojo\..*,^struts\..*</param> 31 </interceptor-ref> 32 33 <interceptor-ref name="conversionError" /> 34 <interceptor-ref name="validation"> 35 <param name="excludeMethods">input,back,cancel,browse</param> 36 </interceptor-ref> 37 38 <!-- 处于拦截器栈的最后,必须在params拦截器后面调用,因为至少要把参数传进去才能知道到底有没有出错啊 :) --> 39 <interceptor-ref name="workflow"> 40 <param name="excludeMethods">input,back,cancel,browse</param> 41 <param name="inputResultName">error</param> 42 </interceptor-ref> 43 </interceptor-stack> 44 </interceptors> 45 46 <action name="loginAction" class="struts_practice_001.LoginAction"> 47 <result>/success.jsp</result> 48 <result name="input">/login.jsp</result> 49 <result name="error">/error.jsp</result> 50 </action> 51 </package> 52 53 </struts>
这个时候所有的action发现了fieldError或者actionError的时候返回的结果字串都是error了。
2. 让action实现ValidationWorkflowAware接口,然后可以返回一个字串结果,这种是action级别的,只对当前action有效。
实现方式:实现ValidationWorkflowAware接口:
ValidationWorkflowAware接口如下:
1 package com.opensymphony.xwork2.interceptor; 2 3 /** 4 * <code>ValidationWorkflowAware</code> 5 */ 6 public interface ValidationWorkflowAware { 7 8 String getInputResultName(); 9 }
很明了的代码不用多解释,来action实现它:
1 public class LoginAction extends ActionSupport implements ValidationWorkflowAware { 2 3 private String username; 4 private String passwd; 5 6 @Override 7 public String execute() throws Exception { 8 return SUCCESS; 9 } 10 11 @Override 12 public String getInputResultName() { 13 return ERROR; 14 } 15 16 //这个方法是在DefaultWorkflowInterceptor之前就执行了的,所以错误信息会被放入 17 @Override 18 public void validate() { 19 addFieldError("foo","bar"); 20 } 21 22 public String getUsername() { 23 return username; 24 } 25 26 public void setUsername(String username) { 27 this.username = username; 28 } 29 30 public String getPasswd() { 31 return passwd; 32 } 33 34 public void setPasswd(String passwd) { 35 this.passwd = passwd; 36 } 37 38 }
这就实现了action级别的控制,注意getInputResultName()是在DefaultWorkflowInterceptor执行的时候才会被调用的。
3. 给方法上加上@InputConfig注解,这种是方法级别的,粒度更细,仅对当前正在执行的方法有效。
实现方式:加@InputConfig注解
先看一下这个注解的代码:
1 @Retention(RetentionPolicy.RUNTIME) 2 @Target({ElementType.METHOD}) 3 public @interface InputConfig { 4 String methodName() default ""; 5 String resultName() default Action.INPUT; 6 }
methodName是执行这个方法名的方法使用其返回值,这个可以在方法里面做一些事情动态生成结果字串之类的
resultName是直接使用传进来字符串作为结果字串
action代码:
1 public class LoginAction extends ActionSupport { 2 3 private String username; 4 private String passwd; 5 6 @InputConfig(resultName="addFail") 7 public String add(){ 8 return SUCCESS; 9 } 10 11 @InputConfig(resultName="deleteFail") 12 public String delete(){ 13 return SUCCESS; 14 } 15 16 //这个方法是在DefaultWorkflowInterceptor之前就执行了的,所以错误信息会被放入 17 @Override 18 public void validate() { 19 addFieldError("foo","bar"); 20 } 21 22 public String getUsername() { 23 return username; 24 } 25 26 public void setUsername(String username) { 27 this.username = username; 28 } 29 30 public String getPasswd() { 31 return passwd; 32 } 33 34 public void setPasswd(String passwd) { 35 this.passwd = passwd; 36 } 37 38 }
如此就实现了方法级别的粒度控制。
附录:
来纠正一个误区,就是如下的写法:
1 public class LoginAction extends ActionSupport { 2 3 private String username; 4 private String passwd; 5 6 @Override 7 public String execute() throws Exception { 8 System.out.println(username+":"+passwd); 9 10 addFieldError("foo","bar"); 11 addActionError("fooActionError"); 12 13 return SUCCESS; 14 } 15 16 public String getUsername() { 17 return username; 18 } 19 20 public void setUsername(String username) { 21 this.username = username; 22 } 23 24 public String getPasswd() { 25 return passwd; 26 } 27 28 public void setPasswd(String passwd) { 29 this.passwd = passwd; 30 } 31 32 }
这样写是没什么卵用的,为什么呢?因为要执行的方法是在拦截器栈全都执行完以后才会执行的,也就是说执行DefaultWorkflowInterceptor的时候execute()还没有被执行,所以正确的姿势是借助之前的拦截器的验证方法放入错误信息,比如下面这个就是可以的:
1 public class LoginAction extends ActionSupport { 2 3 private String username; 4 private String passwd; 5 6 @Override 7 public String execute() throws Exception { 8 System.out.println(username+":"+passwd); 9 10 addFieldError("foo","bar"); 11 addActionError("fooActionError"); 12 13 return SUCCESS; 14 } 15 16 //这个方法是在DefaultWorkflowInterceptor之前就执行了的,所以错误信息会被放入 17 @Override 18 public void validate() { 19 addFieldError("foo","bar"); 20 } 21 22 public String getUsername() { 23 return username; 24 } 25 26 public void setUsername(String username) { 27 this.username = username; 28 } 29 30 public String getPasswd() { 31 return passwd; 32 } 33 34 public void setPasswd(String passwd) { 35 this.passwd = passwd; 36 } 37 38 }
至于这个方法为什么会先于execute()被调用,参考另一篇博文:(还木有写吶(=@__@=))