chain:基本用途是構造成一條動作鏈。前一個Action將控制權轉交給后一個Action,而前一個Action的狀態(tài)在后一個Action里仍然保持著。
我現(xiàn)在有一個場景,F(xiàn)irstAction 通過chain的方式,將控制權交給 SecondAction。FirstAction對應的頁面代碼為first.ftl,SecondAction對應的頁面代碼為second.ftl。
假設我們的FirstAction如下定義:
public class FirstAction extends ActionSupport{
private String input1 = null; // 由first.ftl頁面輸入值
private String input2 = null; // 由first.ftl頁面輸入值
private CustomUser user = null;// 并不在first.ftl頁面上有相關元素綁定
public String execute() throws Exception {
//做一些事情,生成了CustomUser
setCustomUser(ret);
return "toSecond";
}
// getter setter
} 意思很明確了,通過first.ftl的輸入,到DB中或其他,生成了我們的CustomUser對象,這個CustomUser對象將要在SecondAction使用。
于是我們想到了要配置FirstAction 的 name為toSecond的 Result type為 chain,將 生成的CustomUser對象傳遞到 SecondAction中,
我們也這樣做了,但是 經(jīng)過調試,發(fā)現(xiàn)在SecondAction中沒有得到 FirstAction中的CustomUser對象。
SecondAction是這樣實現(xiàn)的:
public class SecondAction extends ActionSupport{
private CustomUser user = null;
public String execute() throws Exception {
// 利用user做事情或顯示在頁面上
}
// getter setter
}
看一下ChainingInterceptor.java的實現(xiàn),發(fā)現(xiàn)有這樣的注釋:
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中。
知道了問題所在,就要解決。首先是想換一種方式去做,將我們要傳遞的參數(shù)通過 其他 Result type 如redirectAction去傳遞。
例如:
<result type="redirectAction" name="toSecond">
<param name="actionName">SecondAction</param>
<param name="method">execute</param>
<param name="user">${user}</param>
</result> 但這樣做的缺點是,
1.我們要在瀏覽器上看到很長很亂的URL(如果超過URL長度限制那就更悲劇了)。
2.暴露這些參數(shù)總感覺很不爽。
3.自定義的對象不能用這種方式傳遞,要么傳String、或JsonObject等。
另外一個解決辦法:
因為Result type為chain時,在執(zhí)行SecondAction時,它的上一個Action,也就是FirstAction的實例并沒有被銷毀,F(xiàn)irstAction的實例被加入到了ValueStack中。
所以,實現(xiàn)的思路就是,增加一個攔截器,在執(zhí)行Actioin前判斷一下,當前Action是否需要從前面的Action實例中獲取數(shù)據(jù)。
這個可以通過注解的方式告訴攔截器,當前的action需要什么樣的對象。
思路明確了,來看看代碼:
注解類:ChainTransParam.java
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ChainTransParam {
String fieldName() default "";
} 攔截器實現(xiàn):ChainParameterInterceptor.java
/**
* Result type 為chain時 可通過注解的方式實現(xiàn)參數(shù)傳遞 此參數(shù)為前置Action的成員變量、并提供getter方法
* 此參數(shù)并不要求一定要在值棧中
*
* @author liming
*/
public class ChainParameterInterceptor extends AbstractInterceptor {
private static final long serialVersionUID = -8279316685527646358L;
@Override
public String intercept(ActionInvocation invocation) throws Exception {
ValueStack stack = invocation.getStack();
CompoundRoot root = stack.getRoot();
// 值棧不為null 且已經(jīng)有前置Action
// 棧最頂層(index = 0)為當前Action、緊接著(index = 1) 為前置Action
if (root == null || root.size() <= 2) {
return invocation.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);
// 取得源數(shù)據(jù)字段名
String fromName = rData.fieldName();
fromName = StringUtils.isEmpty(fromName) ? field.getName() : fromName;
// 取得最近的前置Action
Object srcAction = root.get(1);
// 取得對應字段的值
Object value = ReflectionUtils.getFieldValue(srcAction, srcAction.getClass(), field.getName());
// 設定值
ReflectionUtils.setFieldValue(target, field.getName(), field.getType(), value);
}
}
return invocation.invoke();
}
@SuppressWarnings("unused")
private Object findFieldValue(CompoundRoot root, Field field) {
Object value = null;
int size = root.size();
// 按順序遍歷前置Action
for (int index = 1; index < size; index++) {
Object srcAction = root.get(index);
Object tmp = ReflectionUtils.getFieldValue(srcAction, srcAction.getClass(), field.getName());
// 取得對應字段的值 則返回
// 問題:如果前置Action中該字段本身就為null 則無法處理
if (tmp != null) {
break;
}
}
return value;
}
} 在攔截器的實現(xiàn)中,我是只取得前一個Action中的數(shù)據(jù),并沒有迭代尋找整個ValueStack的Action,也是可以這樣實現(xiàn)的,請看我的findFieldValue方法的實現(xiàn),但這個方法在此攔截器中并沒有使用上。因為我不想這樣做。
代碼完畢之后,配置好攔截器,
我們只要在 SecondAction中 這樣定義即可:
public class SecondAction extends ActionSupport{
@ChainTransParam
private CustomUser user = null;
public String execute() throws Exception {
// 利用user做事情或顯示在頁面上
}
// getter setter
} 當在執(zhí)行SecondAction之前,攔截器會去查找FirstAction,是否有 user 對象,有則將值拷貝到 SecondAction 中。
ChainTransParam 注解 允許輸入?yún)?shù)名,沒有輸入則默認根據(jù)變量名去查找。
注:Struts2 Reference里的意思是不提倡使用Result Type Chain。
另:ReflectionUtils.java 實現(xiàn):
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public abstract class ReflectionUtils {
private static final Log logger = LogFactory.getLog(ReflectionUtils.class);
public static void setFieldValue(Object target, String fname, Class<?> ftype, Object fvalue) {
setFieldValue(target, target.getClass(), fname, ftype, fvalue);
}
public static void setFieldValue(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);
}
}
}
}
public static Object getFieldValue(Object target, String fname) {
return getFieldValue(target, target.getClass(), fname);
}
public static Object getFieldValue(Object target, Class<?> clazz, String fname) {
if (target == null || fname == null || "".equals(fname)) {
return null;
}
boolean exCatched = false;
try {
String methodname = "get" + StringUtils.capitalize(fname);
Method method = clazz.getDeclaredMethod(methodname);
//if (!Modifier.isPublic(method.getModifiers())) {
method.setAccessible(true);
//}
return method.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);
//}
return field.get(target);
}
catch (Exception fe) {
if (logger.isDebugEnabled()) {
logger.debug(fe);
}
}
}
return null;
}
}