本文為原創,歡迎轉載,轉載請注明出處BlogJava。
chain:基本用途是構造成一條動作鏈。前一個Action將控制權轉交給后一個Action,而前一個Action的狀態在后一個Action里仍然保持著。
我現在有一個場景,FirstAction 通過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中,
我們也這樣做了,但是 經過調試,發現在SecondAction中沒有得到 FirstAction中的CustomUser對象。
SecondAction是這樣實現的:

public class SecondAction extends ActionSupport
{
private CustomUser user = null;


public String execute() throws Exception
{
// 利用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去傳遞。
例如:
<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.暴露這些參數總感覺很不爽。
3.自定義的對象不能用這種方式傳遞,要么傳String、或JsonObject等。
另外一個解決辦法:
因為Result type為chain時,在執行SecondAction時,它的上一個Action,也就是FirstAction的實例并沒有被銷毀,FirstAction的實例被加入到了ValueStack中。
所以,實現的思路就是,增加一個攔截器,在執行Actioin前判斷一下,當前Action是否需要從前面的Action實例中獲取數據。
這個可以通過注解的方式告訴攔截器,當前的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 "";
}
攔截器實現:ChainParameterInterceptor.java

/** *//**
* Result type 為chain時 可通過注解的方式實現參數傳遞 此參數為前置Action的成員變量、并提供getter方法
* 此參數并不要求一定要在值棧中
*
* @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 且已經有前置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);
// 取得源數據字段名
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;
}
}
在攔截器的實現中,我是只取得前一個Action中的數據,并沒有迭代尋找整個ValueStack的Action,也是可以這樣實現的,請看我的findFieldValue方法的實現,但這個方法在此攔截器中并沒有使用上。因為我不想這樣做。
代碼完畢之后,配置好攔截器,
我們只要在 SecondAction中 這樣定義即可:

public class SecondAction extends ActionSupport
{
@ChainTransParam
private CustomUser user = null;


public String execute() throws Exception
{
// 利用user做事情或顯示在頁面上
}

// getter setter
}
當在執行SecondAction之前,攔截器會去查找FirstAction,是否有 user 對象,有則將值拷貝到 SecondAction 中。
ChainTransParam 注解 允許輸入參數名,沒有輸入則默認根據變量名去查找。
注:Struts2 Reference里的意思是不提倡使用Result Type Chain。
另:ReflectionUtils.java 實現:
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;
}

}