接<SpringMVC源码分析(5)剖析重要组件HandlerMapping>,继续剖析HandlerMapping,DefaultAnnotationHandlerMapping是SpringMVC 中最重要的HandlerMapping组件。虽然它在spring3.1版本后被废弃了。
包括2部分内容
- DefaultAnnotationHandlerMapping剖析
- HandlerMapping的拦截器
1.DefaultAnnotationHandlerMapping剖析
鉴于它的重要地位,贴下结构图
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方法。
//按方法逐个生成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配置
<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的标签解析过程,很容易配置一个自定义程度比较高的处理器类。确点就是很繁琐
如下
这一段代码虽然可以运行,但缺少类型转换拦截器,需要配置。
<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。
源码解析,一定不要停留在设置表面,要洞察底层细节。
当然,是默认配置好,还是原生态的配置好,一个是简介透明,一个自定义程度高,各取所需。