首页 > 代码库 > SpringMVC源码分析(6)剖析DefaultAnnotationHandlerMapping

SpringMVC源码分析(6)剖析DefaultAnnotationHandlerMapping

接<SpringMVC源码分析(5)剖析重要组件HandlerMapping>,继续剖析HandlerMapping,DefaultAnnotationHandlerMapping是SpringMVC 中最重要的HandlerMapping组件。虽然它在spring3.1版本后被废弃了。

包括2部分内容

  1. DefaultAnnotationHandlerMapping剖析

  2. 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的主要职责

  1. 注册Handler.可以是注解,可以是XML声明。

  2. 生成url,有很多策略。beanName,前缀,包名称都可以作为参考

  3. 维护mapping关系

  4. 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。

源码解析,一定不要停留在设置表面,要洞察底层细节。

当然,是默认配置好,还是原生态的配置好,一个是简介透明,一个自定义程度高,各取所需。



本文出自 “简单” 博客,请务必保留此出处http://dba10g.blog.51cto.com/764602/1879103

SpringMVC源码分析(6)剖析DefaultAnnotationHandlerMapping