修改SpringMVC源码使之能够智能地装配多个参数Bean

记得以前用Struts2时候,有种说法好像叫做自动装配(可能名字记错了,见谅),就是将request中的form表单自动组装为java对象。然而后来使用SpringMVC之后,发现SpringMVC这一点做得有些差强人意。

起因描述

因为Struts2能够自动组装的是这种request参数名:

1
<input name="obj.property">

而SpringMVC自动组装的是这种参数名:

1
<input name="property">

虽然仅仅少了个前缀,但是用起来差别很大。比如在管理系统中,有时候一个form表单中会涉及多个类。我最近遇到的一个form表单中有两种类,一个是辅料,一种是原料。两种原料中都有一些同名的参数mark。在form表单中是这样的:

1
2
3
4
5
6
7
<form>  
......<!--省去代码若干-->
<input type='text' name='mark'/> <!-- 原料的mark -->
......<!--省去代码若干-->
<input type='text' name='mark'/> <!-- 辅料的mark -->
......<!--省去代码若干-->
</form>

这种情况下,我感觉使用SpringMVC很麻烦,或许是自己知识短浅,找不到解决办法。

为什么SpringMVC不能像Struts2一样,可以再name标签上添加一个参数前缀呢?例如:

1
2
3
4
5
6
7
<form>  
......<!--省去代码若干-->
<input type='y.text' name='mark'/> <!-- 原料的mark,action中原料参数名为y -->
......<!--省去代码若干-->
<input type='f.text' name='mark'/> <!-- 辅料的mark,action中辅料参数名为f -->
......<!--省去代码若干-->
</form>

可惜, SpringMVC没有这个功能,因此我便动了修改它源码的念头。经过一番折腾,终于完成了。

修改Spring源码以扩展参数自动装配功能

需要修改的Spring类全部都在org.springframework.web.jar包中,我使用的是3.0.6版本。

第一步

修改org.springframework.web.bind.annotation.support.HandlerMethodInvoker类的
resolveModelAttribute方法:

1
2
3
4
5
6
7
8
9
private WebDataBinder resolveModelAttribute(String attrName,  
MethodParameter methodParam, ExtendedModelMap implicitModel,
NativeWebRequest webRequest, Object handler) throws Exception {
// .......省去若干行
// 原Spring中为name,应将name改为methodParam.getParameterName()
WebDataBinder binder = createBinder(webRequest, bindObject, methodParam.getParameterName());
initBinder(handler, name, binder, webRequest);
return binder;
}

第二步

修改org.springframework.web.bind.ServletRequestParameterPropertyValues类,新增两个方法 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/** 
* 新增的构造方法,此方法添加了一个ServletRequestDataBinder类型参数,用于获取方法arg名称
* @param request
* @param binder
*/
public ServletRequestParameterPropertyValues(ServletRequest request, ServletRequestDataBinder binder){
// super(WebUtils.getParametersStartingWith(request, (prefix != null ? prefix + prefixSeparator : null)));
super(getAvailableParams(request, binder));
}

/**
* 新增方法,此方法内部将target.property提取为property
* @param request
* @param binder
* @return
*/
public static Map<String, Object> getAvailableParams(ServletRequest request, ServletRequestDataBinder binder){
// 此方法内部优先提取target.property值,之后再取property值
Map<String, Object> params = new HashMap<String, Object>();
Enumeration<String> paramNames = request.getParameterNames();
while (paramNames != null && paramNames.hasMoreElements()) {
String paramName = (String) paramNames.nextElement(); // parameter名称
String[] values = request.getParameterValues(paramName);
// 如果是以target.开始的参数则去除target.
if(paramName.startsWith(binder.getObjectName()+".")){
paramName = paramName.substring(paramName.indexOf('.')+1);
}else{
if(params.containsKey(paramName))
continue;
}
if (values == null || values.length == 0) {
// Do nothing, no values found at all.
} else if (values.length > 1) {
params.put(paramName, values);
} else {
params.put(paramName, values[0]);
}
}
return params;
}

第三步

修改org.springframework.web.bind.ServletRequestDataBinder类的
bind方法:

1
2
3
4
5
6
7
8
9
public void bind(ServletRequest request) {  
// 此处原来为new ServletRequestParameterPropertyValues(request)
MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request, this);
MultipartRequest multipartRequest = WebUtils.getNativeRequest(request, MultipartRequest.class);
if (multipartRequest != null) {
bindMultipart(multipartRequest.getMultiFileMap(), mpvs);
}
doBind(mpvs);
}

大功告成

此时,你便可以将form表单中的同名不同类参数修改为obj.property,以标明同名的参数分别归哪个对象。