继续剖析HandlerMapping,DefaultAnnotationHandlerMapping是SpringMVC 中最重要的HandlerMapping组件。虽然它在spring3.1版本后被废弃了。
包括2部分内容
-
DefaultAnnotationHandlerMapping剖析
-
HandlerMapping的拦截器
1.DefaultAnnotationHandlerMapping剖析
鉴于它的重要地位,贴下结构图
1
2
3
4
5
6
7
8
|
public
class
DefaultAnnotationHandlerMapping
extends
AbstractDetectingUrlHandlerMapping {
//是否使用后缀注册url(比如如果注册了/users,同时也会注册 /users.* 和/users/
private
boolean
useDefaultSuffixPattern =
true
;
//缓存handler和requestMapping条件关系,验证时使用
private
final
Map<Class, RequestMapping> cachedMappings =
new
HashMap<Class, RequestMapping>();
...
}
|
1.1. 重写determineUrlsForHandler方法
AbstractDetectingUrlHandlerMapping的子类,重写determineUrlsForHandler方法;
initApplicationContext时被调用。下图为determineUrlsForHandler调用上下文。在springmvc容器初始化过程中调用。
在上篇文章中总结过,HandlerMapping的主要职责
-
注册Handler.可以是注解,可以是XML声明。
-
生成url,有很多策略。beanName,前缀,包名称都可以作为参考
-
维护mapping关系
-
url的匹配能力
DefaultAnnotationHandlerMapping也是如此,determineUrlsForHandler方法,根据方法名就可以猜到,“为Handler匹配URL”。和ControllerClassNameHandlerMapping,ControllerBeanNameHandlerMapping之流是办的事一样的。区别就是他们是基于XML定义的,灵活性不足。DefaultAnnotationHandlerMapping灵活性和功能上更强大而已,名称从RequestMapping注解中获取。具体可以参考determineUrlsForHandlerMethods方法。
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
|
//按方法逐个生成URL
protected
String[] determineUrlsForHandlerMethods(Class<?> handlerType,
final
boolean
hasTypeLevelMapping) {
String[] subclassResult = determineUrlsForHandlerMethods(handlerType);
if
(subclassResult !=
null
) {
return
subclassResult;
}
final
Set<String> urls =
new
LinkedHashSet<String>();
Set<Class<?>> handlerTypes =
new
LinkedHashSet<Class<?>>();
handlerTypes.add(handlerType);
handlerTypes.addAll(Arrays.asList(handlerType.getInterfaces()));
for
(Class<?> currentHandlerType : handlerTypes) {
ReflectionUtils.doWithMethods(currentHandlerType,
new
ReflectionUtils.MethodCallback() {
public
void
doWith(Method method) {
RequestMapping mapping = AnnotationUtils.findAnnotation(method, RequestMapping.
class
);
if
(mapping !=
null
) {
String[] mappedPatterns = mapping.value();
if
(mappedPatterns.length >
0
) {
for
(String mappedPattern : mappedPatterns) {
if
(!hasTypeLevelMapping && !mappedPattern.startsWith(
"/"
)) {
mappedPattern =
"/"
+ mappedPattern;
}
addUrlsForPath(urls, mappedPattern);
}
}
else
if
(hasTypeLevelMapping) {
// empty method-level RequestMapping
urls.add(
null
);
}
}
}
}, ReflectionUtils.USER_DECLARED_METHODS);
}
return
StringUtils.toStringArray(urls);
}
|
2.HandlerMapping的拦截器
2.1 Interceptor位置
从下面的结构图中可以看出,拦截器分布在2个位置,分为2类。
MappedInterceptor是与url绑定的,对符合条件的URL进行拦截;
Interceptor是属于全局范围的,对所有请求进行进行拦截。
2.2 Interceptor的类结构
UserRoleAuthorizationInterceptor | 检查当前用户的授权 |
PathExposingHandlerInterceptor | 暴露bestMatchingPattern |
ConversionServiceExposingInterceptor |
暴露 ConversionService |
ThemeChangeInterceptor | 支持主题切换 |
LocaleChangeInterceptor | 支持切换语言 |
UriTemplateVariablesHandlerInterceptor | 暴露请求变量 |
WebContentInterceptor | 检查,准备请求和响应 |
值得关注的是WebRequestInterceptor。需要专门开辟一章研究。
2.3 默认intercepter配置
1
2
3
4
5
6
7
8
|
<
mvc:interceptors
>
<
bean
class
=
"org.springframework.web.servlet.i18n.LocaleChangeInterceptor"
/>
<
bean
class
=
"org.springframework.web.servlet.handler.UserRoleAuthorizationInterceptor"
/>
<
mvc:interceptor
>
<
mvc:mapping
path
=
"/account"
/>
<
bean
class
=
"org.springframework.web.servlet.theme.ThemeChangeInterceptor"
></
bean
>
</
mvc:interceptor
>
</
mvc:interceptors
>
|
这是一段最普通的声明
我有了个误解:<mvc:interceptors>的子标签<bean>声明的拦截器会设置在interceptor中,结果不是如此。
ConversionServiceExposingInterceptor是在解析标签时,默认注册的。<SpringMVC源码分析(1)标签解析>文章中提到过。
2.4 没事找事型的配置
了解了spring mvc的标签解析过程,很容易配置一个自定义程度比较高的处理器类。确点就是很繁琐
如下
这一段代码虽然可以运行,但缺少类型转换拦截器,需要配置。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
<
bean
class
=
"org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping"
>
<
property
name
=
"interceptors"
>
<
array
>
<
bean
class
=
"org.springframework.web.servlet.i18n.LocaleChangeInterceptor"
/>
</
array
>
</
property
>
<
property
name
=
"mappedInterceptors"
>
<
list
>
<
bean
class
=
"org.springframework.web.servlet.handler.MappedInterceptor"
>
<
constructor-arg
index
=
"0"
>
<
list
>
<
value
>/account</
value
>
</
list
>
</
constructor-arg
>
<
constructor-arg
index
=
"1"
>
<
bean
class
=
"org.springframework.web.servlet.theme.ThemeChangeInterceptor"
></
bean
>
</
constructor-arg
>
</
bean
>
</
list
>
</
property
>
</
bean
>
|
如此声明,一定要把 <mvc:annotation-driven />注释掉,否则系统会存在两个DefaultAnnotationHandlerMapping。
个人感觉这一段和上面的<mvc:interceptors>效果是一样的。
区别
仅是LocaleChangeInterceptor这样公共的拦截器设置在了interceptors属性上,
而不是mappedInterceptors。
源码解析,一定不要停留在设置表面,要洞察底层细节。
当然,是默认配置好,还是原生态的配置好,一个是简介透明,一个自定义程度高,各取所需。
本文转自 randy_shandong 51CTO博客,原文链接:http://blog.51cto.com/dba10g/1879103,如需转载请自行联系原作者