公司里的项目用的框架是常见的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就由每个子类去实现。
我们先不管具体的赋值操作时如何实现的,这种想法比较简单,实现起来也很容易。但是我们都知道struts2里的核心是拦截器,在处理这个问题的时候实际上也是使用了param拦截器,这种做法说不上什么不好,只是既然我们想模拟它,那就模拟的更像一点吧。于是又有了第二种做法。
第二种就是通过使用spring aop来实现,很明显这个赋值操作应该在action的execute方法执行前进行,我们这里就使用spring里的前置通知(也有叫前置增强的)来模拟struts2的param拦截器了。
接着我们就要为每个action来配置这个前置通知了,这里我们使用自动创建代理的方式来配置。
之后,所有的action的成员变量在调用execute方法之前就会被表单里的值所填充。
这里使用DefaultAdvisorAutoProxyCreator为所有的action bean来创建代理,其实我原先是想用BeanNameAutoProxyCreator来为指定的action bean来创建代理的,但是不知道为什么如果我把action bean配置成singleton="false",那么就无法被代理,而我有试过对于一般singleton="false"的bean,代理是会配置上去的。这个问题谁知道的话,希望指导一下。
最后我们再研究下如何为action里的成员变量赋值,如果成员变量都是简单类型,那么很简单,但是若成员变量是复杂类型,比如有个自定义的User类型,User类型里又有一个自定义的Address类型,而表单中提交的键值对是user.address.name=110,这样我们就要递归的创建外层对象,为最内层变量赋值,最后把对象设置到action中去。这里我们用反射来实现。
再做一个测试action
一切都配置好之后,可以再浏览器中输入http://localhost:8080/case1/test.do?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发布运行。注意项目的包结构和文章里的有点不同。
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发布运行。注意项目的包结构和文章里的有点不同。