基于约定的Spring MVC扩展

闲来无事翻了下以前写的项目,发现一个还算不错的东西,就是基于约定的Spring MVC扩展,这段代码是好早以前的东西了(大概四五年了吧),那个时候Spring还远没有现在这么“强大”,哦不,应该说是杂,现在的Spring似乎无所不能,却再也不那么专注了,基于让我有点怀念Spring1.X时代了。

这个扩展是当时没有Annotation时代,为了解决XML配置文件膨胀而产生的,原理很简单,就是依据请求的urlPath,动态的解析到所对应的处理类,然后实例化处理类,注入所需要的依赖,再执行。帖出来怀念一下。

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
package cn.edu.ccnu.inc.webtemplate.spring.mvc;  

import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
import org.springframework.web.servlet.mvc.multiaction.InternalPathMethodNameResolver;
import org.springframework.web.servlet.mvc.multiaction.MethodNameResolver;
import org.springframework.web.servlet.mvc.multiaction.MultiActionController;

import cn.edu.ccnu.inc.webtemplate.cache.SystemCacheManager;
import cn.edu.ccnu.inc.webtemplate.util.ClassUtils;

/**
* Convention based url handler mapping, to find a handle class based on the url hierarchy if no
* handler found with the mapping-configuration.
* Example:
* <bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
* <property name="defaultHandler">
* <!-- It also could configure a reference to an exist handler -->
* <bean class="org.springframework.web.servlet.mvc.UrlFilenameViewController" />
* </property>
* <property name="packages">
* <value>cn.edu.ccnu.inc.webtemplate.controller, cn.edu.ccnu.inc.webtemplate.spring.controller</value>
* </property>
* <property name="mappings">
* <value>
* /login.html = filenameController
* /index.html = filenameController
* /main.html = filenameController
* /security/userManage.html = filenameController
* /security/roleManage.html = filenameController
* </value>
* </property>
* </bean>
* the path "/security/methodPrivilegeManage.html" doesn't match any item of above, so it will be mapped
* to the security.UserManager class under the package cn.edu.ccnu.inc.webtemplate.controller or under
* cn.edu.ccnu.inc.webtemplate.spring.controller package to handle. When no controller found with this strategy,
* a defaultHandler will be used if it is configured(eg..
* @author <a href="mailto:huangfengjing@gmail.com">Ivan</a>
* @created 2005-10-29 下午12:41:30
*/
public class ConventionBasedUrlHandlerMapping extends SimpleUrlHandlerMapping implements InitializingBean {

SystemCacheManager systemCacheManager = null;
private String CACHE_NAME_IN_SYSTEM = "_system_cache_handler";

/** Default handler when no handler mapped */
private Object defaultHandler;

/** Packages to look for the Handler class */
private String packages;

/**
* Config the packages
* @param packages
*/
public void setPackages(String packages) {
this.packages = packages;
}

/**
* @param defaultHandler the defaultHandler to set
*/
public void setDefaultHandler(Object defaultHandler) {
this.defaultHandler = defaultHandler;
}


/**
* Override the default lookup strategy, when the parent can not find an appropriate one to handle.
* Lookup strategy:
* 1. ask parent mapping to look up handler.
* 2. if no handler found, then use url hierarchy build a short name to retrieve the bean as the handler.
* 3. if still no handler found, assemble a full class name with package and url hierarchy, then load and
* initiate a class with reflection, inject all needed properties, finally use this object as the handler.
* 4. finally, if no handler found through all of above ways, use a default one.
* 5. if no default handler specified, return null.
*/
@Override
protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
Object handler = super.lookupHandler(urlPath, request);

Map<Object, Object> handlerCache = systemCacheManager.getCache(CACHE_NAME_IN_SYSTEM);
// get handler from the cache
handler = handlerCache.get(urlPath);
if(handler != null) {
return handler;
}

// load the Controller as the handler
String className;
String[] pkgs = StringUtils.commaDelimitedListToStringArray(packages);

for(String pkg : pkgs) {
className = pkg + "." + ClassUtils.convertUrlPathToClassName(urlPath, true);
handler = ClassUtils.loadModelWithApplicationContextFirst(className, getApplicationContext(), null);

if((handler != null && handler instanceof MultiActionController) && ((MultiActionController)handler).getMethodNameResolver().getClass().equals(InternalPathMethodNameResolver.class)) {
try {
MethodNameResolver methodNameResolver = (MethodNameResolver)BeanFactoryUtils.beanOfTypeIncludingAncestors(getApplicationContext(), MethodNameResolver.class, true, true);
((MultiActionController)handler).setMethodNameResolver(methodNameResolver);
} catch (BeansException be) {
// ignore
}
break;
}
}

// use the default handler if it is specified
if(handler == null && defaultHandler != null) {
handler = defaultHandler;
}

// if find one, put it to the cache
if(handler != null) {
handlerCache.put(urlPath, handler);
}
return handler;
}

/* (non-Javadoc)
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*/
public void afterPropertiesSet() throws Exception {
Assert.notNull(packages, "Packages can not be null.");
if(systemCacheManager == null) {
systemCacheManager = (SystemCacheManager)BeanFactoryUtils.beanOfTypeIncludingAncestors(getApplicationContext(), SystemCacheManager.class);
}
}
}
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
package cn.edu.ccnu.inc.webtemplate.util;  

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.util.ReflectionUtils.FieldFilter;

/**
*
* @author <a href="mailto:huangfengjing@gmail.com">Ivan</a>
* @created 2005-11-1 上午12:40:04
*/
public abstract class ClassUtils extends org.springframework.util.ClassUtils {

/**
* Normalize the control/userManage.html/.htm/.jsp/.vm... name to the standard
* class name like control.UserManage
* @param urlPath
* @return
*/
public static String convertUrlPathToClassName(String urlPath, boolean hasSuffix) {
if(urlPath.startsWith("/")) {
urlPath = urlPath.substring(1);
}
urlPath = urlPath.replaceAll("/", ".");

int index = urlPath.length();
if(hasSuffix) {
// get rid of resource suffix such as .html/.htm/.jsp/.vm and so no
index = urlPath.lastIndexOf(".");
if(index < 0) {
return "";
}
}

String handlerName = urlPath.substring(0, index);
index = handlerName.lastIndexOf(".");
StringBuffer sb = new StringBuffer(handlerName);
if(index < handlerName.length() && Character.isLowerCase(handlerName.charAt(index + 1))) {
sb.setCharAt(index + 1, Character.toUpperCase(handlerName.charAt(index + 1)));
}

return sb.toString();
}

/**
* Load a module class from application context, if not found, then use reflection to initiate one
* and inject all it's properties which is not set yet and the value has been configured in context.
* @param fullname
* @param context
* @return
*/
public static Object loadModelWithApplicationContextFirst(String fullname, ApplicationContext context, FieldFilter filter) {
Object model = null;

String shortName = ClassUtils.getShortName(fullname);
StringBuffer sb = new StringBuffer(shortName);
sb.setCharAt(0, Character.toLowerCase(shortName.charAt(0)));
try {
model = context.getBean(sb.toString());
} catch (BeansException be) {
// ignore
}
if(model != null) {
return model;
}

try {
model = ClassUtils.forName(fullname).newInstance();
injectDependencies(model, context, filter);
} catch (ClassNotFoundException e) {
// ignore, continue
} catch (InstantiationException e) {
// ignore, continue
} catch (IllegalAccessException e) {
// ignore, continue
}

return model;
}

/**
* Initialize all the properties which has Setter, the value comes from Spring ApplictionContext
* By default, the static/final/volatile/native field will no be injected.
* @param bean
*/
public static void injectDependencies(Object bean, ApplicationContext context, FieldFilter filter) {
Method[] methods = ReflectionUtils.getAllDeclaredMethods(bean.getClass());
Field[] fields = ReflectionUtils.getAllFields(bean.getClass());
for(Field field : fields) {
if(filter == null) {
filter = new FieldFilter() {
public boolean matches(Field field) {
int modifier = field.getModifiers();
if(Modifier.isStatic(modifier) || Modifier.isFinal(modifier)
|| Modifier.isVolatile(modifier) || Modifier.isNative(modifier)) {
return false;
}
return true;
}
};
}
if(!filter.matches(field)) {
continue;
}
ReflectionUtils.makeAccessible(field);
try {
if(field.get(bean) != null) {
continue;
}
} catch (Exception e) {
continue;
}

// make sure it has a Setter or we will skip this field
StringBuffer startdMethodName = new StringBuffer(field.getName());
startdMethodName.setCharAt(0, Character.toUpperCase(field.getName().charAt(0)));
startdMethodName.insert(0, "set");
for(Method method : methods) {
if((startdMethodName.toString()).equals(method.getName())) {
try {
ReflectionUtils.invokeMethod(method, bean, new Object[] {context.getBean(field.getName())});
break;
} catch (BeansException be) {
// ignore
}
}
}
}
}
}