本文为原创,欢迎转载,转载请注明出处BlogJava。
chain:基本用途是构造成一条动作链。前一个Action将控制权转交给后一个Action,而前一个Action的状态在后一个Action里仍然保持着。
我现在有一个场景,FirstAction 通过chain的方式,将控制权交给 SecondAction。FirstAction对应的页面代码为first.ftl,SecondAction对应的页面代码为second.ftl。
假设我们的FirstAction如下定义:
publicclassFirstActionextendsActionSupport{
privateString input1=null;//由first.ftl页面输入值privateString input2=null;//由first.ftl页面输入值privateCustomUser user=null;//并不在first.ftl页面上有相关元素绑定
publicString execute()throwsException{
//做一些事情,生成了CustomUsersetCustomUser(ret);
return"toSecond";
}
//getter setter}
意思很明确了,通过first.ftl的输入,到DB中或其他,生成了我们的CustomUser对象,这个CustomUser对象将要在SecondAction使用。
于是我们想到了要配置FirstAction 的 name为toSecond的 Result type为 chain,将 生成的CustomUser对象传递到 SecondAction中,
我们也这样做了,但是 经过调试,发现在SecondAction中没有得到 FirstAction中的CustomUser对象。
SecondAction是这样实现的:
publicclassSecondActionextendsActionSupport{
privateCustomUser user=null;
publicString execute()throwsException{
//利用user做事情或显示在页面上}
//getter setter}
看一下ChainingInterceptor.java的实现,发现有这样的注释:
An interceptor that copies all the properties of every object in the value stack to the currently executing object
.
在 FirstAction 中CustomUser user 并没有在 value stack 中,所以没有拷贝到SecondAction中。
知道了问题所在,就要解决。首先是想换一种方式去做,将我们要传递的参数通过 其他 Result type 如redirectAction去传递。
例如:
SecondActionexecute${user}
但这样做的缺点是,
1.我们要在浏览器上看到很长很乱的URL(如果超过URL长度限制那就更悲剧了)。
2.暴露这些参数总感觉很不爽。
3.自定义的对象不能用这种方式传递,要么传String、或JsonObject等。
另外一个解决办法:
因为Result type为chain时,在执行SecondAction时,它的上一个Action,也就是FirstAction的实例并没有被销毁,FirstAction的实例被加入到了ValueStack中。
所以,实现的思路就是,增加一个拦截器,在执行Actioin前判断一下,当前Action是否需要从前面的Action实例中获取数据。
这个可以通过注解的方式告诉拦截器,当前的action需要什么样的对象。
思路明确了,来看看代码:
注解类:ChainTransParam.java
importjava.lang.annotation.Documented;
importjava.lang.annotation.ElementType;
importjava.lang.annotation.Retention;
importjava.lang.annotation.RetentionPolicy;
importjava.lang.annotation.Target;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public@interfaceChainTransParam{
String fieldName()default"";
}
拦截器实现:ChainParameterInterceptor.java
/** *//** * Result type 为chain时 可通过注解的方式实现参数传递 此参数为前置Action的成员变量、并提供getter方法
* 此参数并不要求一定要在值栈中
*
*@authorliming
*/
publicclassChainParameterInterceptorextendsAbstractInterceptor{
privatestaticfinallongserialVersionUID=-8279316685527646358L;
@Override
publicString intercept(ActionInvocation invocation)throwsException{
ValueStack stack=invocation.getStack();
CompoundRoot root=stack.getRoot();
//值栈不为null 且已经有前置Action
//栈最顶层(index = 0)为当前Action、紧接着(index = 1) 为前置Action
if(root==null||root.size()<=2){
returninvocation.invoke();
}
//当前Action对象Object target=invocation.getAction();
Field[] fields=target.getClass().getDeclaredFields();
//遍历此Action对象的属性 是否有RecieveData注解
for(Field field : fields){
if(field.isAnnotationPresent(ChainTransParam.class)){
ChainTransParam rData=field.getAnnotation(ChainTransParam.class);
//取得源数据字段名String fromName=rData.fieldName();
fromName=StringUtils.isEmpty(fromName)?field.getName() : fromName;
//取得最近的前置ActionObject srcAction=root.get(1);
//取得对应字段的值Object value=ReflectionUtils.getFieldValue(srcAction, srcAction.getClass(), field.getName());
//设定值ReflectionUtils.setFieldValue(target, field.getName(), field.getType(), value);
} }
returninvocation.invoke();
}
@SuppressWarnings("unused")
privateObject findFieldValue(CompoundRoot root, Field field){
Object value=null;
intsize=root.size();
//按顺序遍历前置Action
for(intindex=1; index{
Object srcAction=root.get(index);
Object tmp=ReflectionUtils.getFieldValue(srcAction, srcAction.getClass(), field.getName());
//取得对应字段的值 则返回
//问题:如果前置Action中该字段本身就为null 则无法处理
if(tmp!=null){
break;
} }
returnvalue;
}}
在拦截器的实现中,我是只取得前一个Action中的数据,并没有迭代寻找整个ValueStack的Action,也是可以这样实现的,请看我的findFieldValue方法的实现,但这个方法在此拦截器中并没有使用上。因为我不想这样做。
代码完毕之后,配置好拦截器,
我们只要在 SecondAction中 这样定义即可:
publicclassSecondActionextendsActionSupport{
@ChainTransParam
privateCustomUser user=null;
publicString execute()throwsException{
//利用user做事情或显示在页面上}
//getter setter}
当在执行SecondAction之前,拦截器会去查找FirstAction,是否有 user 对象,有则将值拷贝到 SecondAction 中。
ChainTransParam 注解 允许输入参数名,没有输入则默认根据变量名去查找。
注:Struts2 Reference里的意思是不提倡使用Result Type Chain。
另:ReflectionUtils.java 实现:
importjava.lang.reflect.Field;
importjava.lang.reflect.InvocationTargetException;
importjava.lang.reflect.Method;
importorg.apache.commons.lang.StringUtils;
importorg.apache.commons.logging.Log;
importorg.apache.commons.logging.LogFactory;
publicabstractclassReflectionUtils{
privatestaticfinalLog logger=LogFactory.getLog(ReflectionUtils.class);
publicstaticvoidsetFieldValue(Object target, String fname, Class>ftype, Object fvalue){
setFieldValue(target, target.getClass(), fname, ftype, fvalue);
}
publicstaticvoidsetFieldValue(Object target, Class>clazz, String fname, Class>ftype, Object fvalue){
if(target==null||fname==null||"".equals(fname)
||(fvalue!=null&&!ftype.isAssignableFrom(fvalue.getClass()))){
return;
}
try{
Method method=clazz.getDeclaredMethod(
"set"+Character.toUpperCase(fname.charAt(0))+fname.substring(1), ftype);
//if (!Modifier.isPublic(method.getModifiers())) {method.setAccessible(true);
//}method.invoke(target, fvalue);
}
catch(Exception me){
if(logger.isDebugEnabled()){
logger.debug(me);
}
try{
Field field=clazz.getDeclaredField(fname);
//if (!Modifier.isPublic(field.getModifiers())) {field.setAccessible(true);
//}field.set(target, fvalue);
}
catch(Exception fe){
if(logger.isDebugEnabled()){
logger.debug(fe);
} } } }
publicstaticObject getFieldValue(Object target, String fname){
returngetFieldValue(target, target.getClass(), fname);
}
publicstaticObject getFieldValue(Object target, Class>clazz, String fname){
if(target==null||fname==null||"".equals(fname)){
returnnull;
}
booleanexCatched=false;
try{
String methodname="get"+StringUtils.capitalize(fname);
Method method=clazz.getDeclaredMethod(methodname);
//if (!Modifier.isPublic(method.getModifiers())) {method.setAccessible(true);
//}returnmethod.invoke(target);
}
catch(NoSuchMethodException e){
exCatched=true;
}
catch(InvocationTargetException e){
exCatched=true;
}
catch(IllegalAccessException e){
exCatched=true;
}
if(exCatched){
try{
Field field=clazz.getDeclaredField(fname);
//if (!Modifier.isPublic(field.getModifiers())) {field.setAccessible(true);
//}returnfield.get(target);
}
catch(Exception fe){
if(logger.isDebugEnabled()){
logger.debug(fe);
} } }returnnull;
}
}