SpringMVC源码分析(6)剖析DefaultAnnotationHandlerMapping

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介:

继续剖析HandlerMapping,DefaultAnnotationHandlerMapping是SpringMVC 中最重要的HandlerMapping组件。虽然它在spring3.1版本后被废弃了。

包括2部分内容

  1. DefaultAnnotationHandlerMapping剖析

  2. HandlerMapping的拦截器

1.DefaultAnnotationHandlerMapping剖析

鉴于它的重要地位,贴下结构图

wKioL1hCKnWx4gYCAACX0CQYtFA954.png

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容器初始化过程中调用。

wKioL1hCK1yhXCRbAABFzPcjfXs824.png

在上篇文章中总结过,HandlerMapping的主要职责

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

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

  3. 维护mapping关系

  4. 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是属于全局范围的,对所有请求进行进行拦截。

wKiom1hCMDzyA3KAAAA1iMyumJU462.png


2.2 Interceptor的类结构

wKioL1hCMruCfElNAACC0tMAcZI252.png


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 >

这是一段最普通的声明

wKioL1hCT1-ByAozAAByb2_2KaM218.png

我有了个误解:<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,如需转载请自行联系原作者

相关文章
|
12月前
|
Java Spring
【Java用法】Spring之@Nullable和@NotNull注释的使用
【Java用法】Spring之@Nullable和@NotNull注释的使用
224 0
|
2月前
|
Java API
SpringBoot Scheduled 常见用法
SpringBoot Scheduled 常见用法
26 0
|
4月前
|
Java 调度 Spring
SpringBoot多个@Scheduled注解的方法,会阻塞吗
【6月更文挑战第9天】SpringBoot多个@Scheduled注解的方法,会阻塞吗
316 5
|
3月前
|
Java Spring
Spring Boot Application in default package
Spring Boot Application in default package
|
4月前
|
Java 数据库
SpringBoot整合es7.x启动报错:Rejecting mapping update to [AAA] as the final mapping would have more than 1
SpringBoot整合es7.x启动报错:Rejecting mapping update to [AAA] as the final mapping would have more than 1
|
5月前
|
Java Spring
解决SpringBoot中@RequestBody不能和Multipart同时传递的问题
请根据你的实际需求选择上述方法之一来解决Spring Boot中 `@RequestBody`不能和 `Multipart`同时使用的问题。
651 1
|
10月前
|
XML 前端开发 JavaScript
SpringMVC中单独配置<mvc:default-servlet-handler/> 导致 Controller失效
SpringMVC中单独配置<mvc:default-servlet-handler/> 导致 Controller失效
228 0
|
12月前
|
安全 Java Go
解决Spring Data JPA中的NullPointerException问题
解决Spring Data JPA中的NullPointerException问题
244 0
|
人工智能 Java 应用服务中间件
SpringBoot实战(十一):MultipartException: Could not parse multipart servlet request
SpringBoot实战(十一):MultipartException: Could not parse multipart servlet request
244 0
|
网络协议 Java 数据库连接
Springboot 通用返回类Result
Springboot 通用返回类Result
517 0