接<SpringMVC源码分析(5)剖析重要组件HandlerMapping>,继续剖析HandlerMapping,DefaultAnnotationHandlerMapping是SpringMVC 中最重要的HandlerMapping组件。虽然它在spring3.1版本后被废弃了。
包括2部分内容
DefaultAnnotationHandlerMapping剖析
HandlerMapping的拦截器
1.DefaultAnnotationHandlerMapping剖析
鉴于它的重要地位,贴下结构图
![111.png wKioL1hCKnWx4gYCAACX0CQYtFA954.png]()
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容器初始化过程中调用。
![233.png wKioL1hCK1yhXCRbAABFzPcjfXs824.png]()
在上篇文章中总结过,HandlerMapping的主要职责
注册Handler.可以是注解,可以是XML声明。
生成url,有很多策略。beanName,前缀,包名称都可以作为参考
维护mapping关系
url的匹配能力
DefaultAnnotationHandlerMapping也是如此,determineUrlsForHandler方法,根据方法名就可以猜到,“为Handler匹配URL”。和ControllerClassNameHandlerMapping,ControllerBeanNameHandlerMapping之流是办的事一样的。区别就是他们是基于XML定义的,灵活性不足。DefaultAnnotationHandlerMapping灵活性和功能上更强大而已,名称从RequestMapping注解中获取。具体可以参考determineUrlsForHandlerMethods方法。
//按方法逐个生成URL
protected String[] determineUrlsForHandlerMethods(Class<?> handlerType, final boolean hasTypeLevelMapping) {
String[] subcla***esult = determineUrlsForHandlerMethods(handlerType);
if (subcla***esult != null) {
return subcla***esult;
}
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是属于全局范围的,对所有请求进行进行拦截。
![1.png wKiom1hCMDzyA3KAAAA1iMyumJU462.png]()
2.2 Interceptor的类结构
![2.png wKioL1hCMruCfElNAACC0tMAcZI252.png]()
| UserRoleAuthorizationInterceptor |
检查当前用户的授权
|
| PathExposingHandlerInterceptor |
暴露bestMatchingPattern |
ConversionServiceExposingInterceptor
|
暴露 ConversionService |
| ThemeChangeInterceptor |
支持主题切换
|
| LocaleChangeInterceptor |
支持切换语言
|
| UriTemplateVariablesHandlerInterceptor |
暴露请求变量
|
| WebContentInterceptor |
检查,准备请求和响应
|
值得关注的是WebRequestInterceptor。需要专门开辟一章研究。
2.3 默认intercepter配置
<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>
这是一段最普通的声明
![3222.png wKioL1hCT1-ByAozAAByb2_2KaM218.png]()
我有了个误解:<mvc:interceptors>的子标签<bean>声明的拦截器会设置在interceptor中,结果不是如此。
ConversionServiceExposingInterceptor是在解析标签时,默认注册的。<SpringMVC源码分析(1)标签解析>文章中提到过。
2.4 没事找事型的配置
了解了spring mvc的标签解析过程,很容易配置一个自定义程度比较高的处理器类。确点就是很繁琐
如下
这一段代码虽然可以运行,但缺少类型转换拦截器,需要配置。
<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。
源码解析,一定不要停留在设置表面,要洞察底层细节。
当然,是默认配置好,还是原生态的配置好,一个是简介透明,一个自定义程度高,各取所需。