讓struts1來模擬struts2

公司裏的項目用的框架是常見的SSH,只是使用的是struts1和spring1,都略顯得有點老舊了。之前看了陣struts2,感覺比struts1先進了很多,但是我想公司是不可能隨便升級框架的,正好這兩天閒着沒什麼事做,琢磨着該做些什麼了。於是我就想讓struts1模擬一些struts2的特性。
struts2取消了actionform,並且使action成爲了多實例的模式,這樣在action裏就可以使用成員變量了,而在使用了param攔截器後,表單中的值還會自動填充action的成員變量。
今天的目標就是讓struts1也來實現這個特性。
首先我們要使struts1的action成爲多實例模式--這樣我們才能在action中使用成員變量,只要讓action由spring來管理就行了。這個很簡單,具體做法略。
接着我們要實現表單的值自動填充action的成員變量。在做這個之前我有兩重想法:
一種是使用類繼承的方式,通過編寫一個抽象的action父類,在這個父類的execute方法裏,在調用正式的業務處理代碼之前,把表單中傳來的值賦給各個action子類的成員變量,然後在一個抽象的方法doExecute中執行具體的業務處理,而這個doExecute就由每個子類去實現。

public abstract class AbstractBaseAction extends Action {

@Override
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response){

//1.爲action的成員便來那個賦值
WebParamUtils.perpareParam(this, request);
//2.業務處理
return doExecute(mapping, form, request, response);

}

//真正處理業務的方法,由子類實現
public abstract ActionForward doExecute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response);

}

我們先不管具體的賦值操作時如何實現的,這種想法比較簡單,實現起來也很容易。但是我們都知道struts2裏的核心是攔截器,在處理這個問題的時候實際上也是使用了param攔截器,這種做法說不上什麼不好,只是既然我們想模擬它,那就模擬的更像一點吧。於是又有了第二種做法。
第二種就是通過使用spring aop來實現,很明顯這個賦值操作應該在action的execute方法執行前進行,我們這裏就使用spring裏的前置通知(也有叫前置增強的)來模擬struts2的param攔截器了。

public class ParamAdvice implements MethodBeforeAdvice {

public void before(Method method, Object[] args, Object target)
throws Throwable {

System.out.println("在action調用execute方法前,爲成員變量賦值");

Action action = null;
HttpServletRequest request = null;

//取出action
if(target instanceof Action){
action = (Action)target;
}

//取出request
for(Object arg : args){
if(arg instanceof HttpServletRequest){
request = (HttpServletRequest)arg;
break;
}
}

//爲成員變量賦值
if(action != null && request != null){
WebParamUtils.perpareParam(action, request);
}

}
}

接着我們就要爲每個action來配置這個前置通知了,這裏我們使用自動創建代理的方式來配置。

<!-- action bean -->
<bean name="/test" class="personal.smy.showcase.web.TestAction" singleton="false"/>

<!-- 切面bean -->
<bean id="paramAdvisor"
class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
<property name="mappedNames">
<list>
<value>execute</value>
</list>
</property>
<property name="advice">
<bean class="personal.smy.modules.web.advice.ParamAdvice" />
</property>
</bean>

<!-- 自動創建代理bean -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>

之後,所有的action的成員變量在調用execute方法之前就會被表單裏的值所填充。
這裏使用DefaultAdvisorAutoProxyCreator爲所有的action bean來創建代理,其實我原先是想用BeanNameAutoProxyCreator來爲指定的action bean來創建代理的,但是不知道爲什麼如果我把action bean配置成singleton="false",那麼就無法被代理,而我有試過對於一般singleton="false"的bean,代理是會配置上去的。這個問題誰知道的話,希望指導一下。
最後我們再研究下如何爲action裏的成員變量賦值,如果成員變量都是簡單類型,那麼很簡單,但是若成員變量是複雜類型,比如有個自定義的User類型,User類型裏又有一個自定義的Address類型,而表單中提交的鍵值對是user.address.name=110,這樣我們就要遞歸的創建外層對象,爲最內層變量賦值,最後把對象設置到action中去。這裏我們用反射來實現。

public class WebParamUtils {

public static void perpareParam(Action action, HttpServletRequest request) {

@SuppressWarnings("unchecked")
Enumeration e = request.getParameterNames();
while (e.hasMoreElements()) {
String fieldName = (String) e.nextElement(); //名
String fieldValue = request.getParameter(fieldName); //值

try {
ReflectionUtils
.setFieldForObject(action, fieldName, fieldValue);
} catch (SecurityException e1) {
e1.printStackTrace();
} catch (IllegalArgumentException e1) {
e1.printStackTrace();
} catch (InstantiationException e1) {
e1.printStackTrace();
} catch (IllegalAccessException e1) {
e1.printStackTrace();
} catch (NoSuchMethodException e1) {
e1.printStackTrace();
} catch (InvocationTargetException e1) {
e1.printStackTrace();
}
}
}
}


public class ReflectionUtils {

//向上獲得類的聲明字段
@SuppressWarnings("unchecked")
public static Field getDeclaredField(final Class clazz,
final String fieldName) {
Assert.notNull(clazz, "clazz不能爲空");
Assert.hasText(fieldName, "fieldName");
for (Class superClass = clazz; superClass != Object.class; superClass = superClass
.getSuperclass()) {
try {
return superClass.getDeclaredField(fieldName);
} catch (NoSuchFieldException e) {
// Field不在當前類定義,繼續向上轉型
}
}
return null;
}

//調用對象的set方法
public static void invokeSetMethod(Object instance, Field field, Object fieldValue)
throws SecurityException, NoSuchMethodException,
IllegalArgumentException, IllegalAccessException,
InvocationTargetException {
String fieldName = field.getName();

String setMethodName = "set" + fieldName.substring(0, 1).toUpperCase()
+ fieldName.substring(1);

Method setMethod = instance.getClass().getMethod(setMethodName,
field.getType());
setMethod.invoke(instance, fieldValue);
}

//調用對象的get方法
public static Object invokeGetMethod(Object instance, Field field)
throws SecurityException, NoSuchMethodException,
IllegalArgumentException, IllegalAccessException,
InvocationTargetException {
String fieldName = field.getName();

String getMethodName = "get" + fieldName.substring(0, 1).toUpperCase()
+ fieldName.substring(1);

Method getMethod = instance.getClass().getMethod(getMethodName, null);
return getMethod.invoke(instance, null);
}

// 獲得指定變量的值
public static Object getFieldValue(Object instance, String fieldName)
throws IllegalArgumentException, IllegalAccessException {

Field field = getDeclaredField(instance.getClass(), fieldName);
return getFieldValue(instance, field);
}

// 獲得指定變量的值
public static Object getFieldValue(Object instance, Field field)
throws IllegalArgumentException, IllegalAccessException {

// 參數值爲true,禁用訪問控制檢查
field.setAccessible(true);
return field.get(instance);
}

//爲對象設置屬性值
public static void setFieldForObject(Object instance, String fieldName,
Object fieldValue) throws InstantiationException,
IllegalAccessException, SecurityException,
IllegalArgumentException, NoSuchMethodException,
InvocationTargetException {

int index = fieldName.indexOf(".");

if (index != -1) { // 要設置的是一個實體對象

String entityName = fieldName.substring(0, index);
String subString = fieldName.substring(index + 1);

Field field = getDeclaredField(instance.getClass(), entityName);

if (field != null) {
Object entity = getFieldValue(instance, field);
if (entity == null) { // 不存在的話就創建實體對象
entity = field.getType().newInstance();
}
// 遞歸
setFieldForObject(entity, subString, fieldValue);
// 把實體對象設置到父對象中
invokeSetMethod(instance, field, entity);
}

} else { // 要設置的是一個簡單類型對象
Field field = ReflectionUtils.getDeclaredField(instance.getClass(),
fieldName);

if (field != null) {
Object value = ConvertUtils
.convert(fieldValue, field.getType()); // 把值轉換爲相應的類型
invokeSetMethod(instance, field, value);
}
}

}
}

再做一個測試action

public class TestAction extends Action{

private int code;

private String msg;

private User user;

@Override
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response) {

System.out.println("========================");
System.out.println("code="+code);
System.out.println("msg="+msg);
System.out.println("user.id="+user.getId());
System.out.println("user.name="+user.getName());
System.out.println("user.address.id="+user.getAddress().getId());
System.out.println("user.address.addr="+user.getAddress().getAddr());

return null;
}

public int getCode() {
return code;
}

public void setCode(int code) {
this.code = code;
}

public String getMsg() {
return msg;
}

public void setMsg(String msg) {
this.msg = msg;
}

public User getUser() {
return user;
}

public void setUser(User user) {
this.user = user;
}

}

一切都配置好之後,可以再瀏覽器中輸入http://localhost:8080/case1/test.do?code=1&msg=2&user.id=3&user.name=4&user.address.id=5&user.address.addr=6,你就會在控制檯上看到

在action調用execute方法前,爲成員變量賦值
========================
code=1
msg=2
user.id=3
user.name=4
user.address.id=5
user.address.addr=6

這樣我們就大功告成了,以上的兩種方案都有經過測試,雖然第二種做法比較新穎一點,但是我們知道spring aop使用了代理,而且這裏還使用了CGLIB代理,而CGLIB並不適於創建多實例模式bean的代理(和jdk動態代理相比),所以性能上估計會有點損失,顯然第一種方案會好點,其實具體的性能怎樣我也沒有測過,有誰感興趣的話可以去試試。

源碼附件已上傳,裏面包含了完整的類庫,直接解壓後即可導入MyEclipse發佈運行。注意項目的包結構和文章裏的有點不同。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章