SpringMVC源码分析(5)剖析重要组件HandlerMapping

简介:

HanlerMapping是沟通请求和后端controller映射,是所有请求的入口。

1.类结构介绍

wKioL1hBbzzTK0_qAAC6ahP8aCY728.png           

该图只对属性和类层级进行了描述,屏蔽了方法,主要是为了根据内部属性,重点理解Spring HandlerMapping提供功能。

1.1 AbstractHandlerMapping

 HandlerMapping 抽象类,提供了排序,默认Handler,和handler 拦截器。

1
2
3
4
5
6
7
8
9
10
11
public  abstract  class  AbstractHandlerMapping  extends  WebApplicationObjectSupport
       implements  HandlerMapping, Ordered {
private  int  order = Integer.MAX_VALUE;   // default: same as non-Ordered
 
private  Object defaultHandler;
 
private  final  List<Object> interceptors =  new  ArrayList<Object>();
 
private  HandlerInterceptor[] adaptedInterceptors;
...
}

属性不做过多解释。

重点说明下interceptors 和adaptedInterceptors两个属性的区别

interceptors :作用于所有的mapping对应的所有Handler;

adaptedInterceptors:转换interceptors的镜像;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
protected  void  initInterceptors() {
    if  (! this .interceptors.isEmpty()) {
       this .adaptedInterceptors =  new  HandlerInterceptor[ this .interceptors.size()];
       for  ( int  i =  0 ; i <  this .interceptors.size(); i++) {
          Object interceptor =  this .interceptors.get(i);
          if  (interceptor ==  null ) {
             throw  new  IllegalArgumentException( "Entry number "  + i +  " in interceptors array is null" );
          }
          this .adaptedInterceptors[i] = adaptInterceptor(interceptor);
       }
    }
}
protected  HandlerInterceptor adaptInterceptor(Object interceptor) {
    if  (interceptor  instanceof  HandlerInterceptor) {
       return  (HandlerInterceptor) interceptor;
    }
    else  if  (interceptor  instanceof  WebRequestInterceptor) {
       return  new  WebRequestHandlerInterceptorAdapter((WebRequestInterceptor) interceptor);
    }
    else  {
       throw  new  IllegalArgumentException( "Interceptor type not supported: "  + interceptor.getClass().getName());
    }
}

1.2 AbstractUrlHandlerMapping

提供 URL-mapped功能,提供了根据url检索handler的能力。

url匹配规则:最长匹配。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public  abstract  class  AbstractUrlHandlerMapping  extends  AbstractHandlerMapping {
     //从请求request找出url
    private  UrlPathHelper urlPathHelper =  new  UrlPathHelper();
     //进行url匹配,选择合适的拦截器
    private  PathMatcher pathMatcher =  new  AntPathMatcher();
     //响应handlerMapping root请求("/")
    private  Object rootHandler;
    // whether to lazily initialize handlers
    private  boolean  lazyInitHandlers =  false ;
     //registered handlers
    private  final  Map<String, Object> handlerMap =  new  LinkedHashMap<String, Object>();
     //MappedInterceptors 
    private  MappedInterceptors mappedInterceptors;
      ...  
  }

1.3 AbstractDetectingUrlHandlerMapping

提供发现HandlerMapping能力.,通过内省(introspection)方式,从spring容器中获取。

具体见detectHandlers。

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
public  abstract  class  AbstractDetectingUrlHandlerMapping  extends  AbstractUrlHandlerMapping {
//whether to detect handler beans in ancestor ApplicationContexts
private  boolean  detectHandlersInAncestorContexts =  false ;
protected  void  detectHandlers()  throws  BeansException {
    if  (logger.isDebugEnabled()) {
       logger.debug( "Looking for URL mappings in application context: "  + getApplicationContext());
    }
    String[] beanNames = ( this .detectHandlersInAncestorContexts ?
          BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object. class ) :
          getApplicationContext().getBeanNamesForType(Object. class ));
 
    // Take any bean name that we can determine URLs for.
    for  (String beanName : beanNames) {
       String[] urls = determineUrlsForHandler(beanName);
       if  (!ObjectUtils.isEmpty(urls)) {
          // URL paths found: Let's consider it a handler.
          registerHandler(urls, beanName);
       }
       else  {
          if  (logger.isDebugEnabled()) {
             logger.debug( "Rejected bean name '"  + beanName +  "': no URL paths identified" );
          }
       }
    }
}
...
}

1.4 AbstractControllerUrlHandlerMapping

提供获得controller到url的转换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public  abstract  class  AbstractControllerUrlHandlerMapping  extends  AbstractDetectingUrlHandlerMapping  {
 
    private  ControllerTypePredicate predicate =  new  AnnotationControllerTypePredicate();
     //Java packages that should be excluded from this mapping
    private  Set<String> excludedPackages = Collections.singleton( "org.springframework.web.servlet.mvc" );
     // controller classes that should be excluded
    private  Set<Class> excludedClasses = Collections.emptySet();
    @Override
     protected  String[] determineUrlsForHandler(String beanName) {
        Class beanClass = getApplicationContext().getType(beanName);
        if  (isEligibleForMapping(beanName, beanClass)) {
           return  buildUrlsForHandler(beanName, beanClass);
        }
        else  {
           return  null ;
        }
     }
...
}

2. 实例

2.1 SimpleUrlHandlerMapping

简单映射关系。

2.2 配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<? xml  version = "1.0"  encoding = "UTF-8" ?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd">
 
< beans >
 
    < bean  id = "urlMapping"  class = "org.springframework.web.servlet.handler.SimpleUrlHandlerMapping" >
       < property  name = "defaultHandler" >< ref  local = "starController" /></ property >
       < property  name = "rootHandler" >< ref  local = "mainController" /></ property >
       < property  name = "urlMap" >
          < map >
             < entry  key = "/welcome*" >< ref  local = "otherController" /></ entry >
             < entry  key = "/welcome.html" >< ref  local = "mainController" /></ entry >
             < entry  key = "/show.html" >< ref  local = "mainController" /></ entry >
             < entry  key = "/bookseats.html" >< ref  local = "mainController" /></ entry >
             < entry  key = "/reservation.html" >< ref  local = "mainController" /></ entry >
             < entry  key = "/payment.html" >< ref  local = "mainController" /></ entry >
             < entry  key = "/confirmation.html" >< ref  local = "mainController" /></ entry >
          </ map >
       </ property >
    </ bean >
    < bean  id = "mainController"  class = "java.lang.Object" />
   < bean  id = "otherController"  class = "java.lang.Object" />
   < bean  id = "starController"   class = "java.lang.Object" />
</ beans >

测试用例

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
@Test
public  void  urlMappingWithUrlMap()  throws  Exception {
    checkMappings( "urlMapping" );
}
private  void  checkMappings(String beanName)  throws  Exception {
    MockServletContext sc =  new  MockServletContext( "" );
    XmlWebApplicationContext wac =  new  XmlWebApplicationContext();
    wac.setServletContext(sc);
    wac.setConfigLocations( new  String[] { "/org/springframework/web/servlet/handler/map2.xml" });
    wac.refresh();
    Object bean = wac.getBean( "mainController" );
    Object otherBean = wac.getBean( "otherController" );
    Object defaultBean = wac.getBean( "starController" );
    HandlerMapping hm = (HandlerMapping) wac.getBean(beanName);
 
    MockHttpServletRequest req =  new  MockHttpServletRequest( "GET" "/welcome.html" );
    HandlerExecutionChain hec = getHandler(hm, req);
    assertTrue( "Handler is correct bean" , hec !=  null  && hec.getHandler() == bean);
    assertEquals( "/welcome.html" , req.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE));
 
    req =  new  MockHttpServletRequest( "GET" "/welcome.x" );
    hec = getHandler(hm, req);
    assertTrue( "Handler is correct bean" , hec !=  null  && hec.getHandler() == otherBean);
    assertEquals( "welcome.x" , req.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE));
 
    req =  new  MockHttpServletRequest( "GET" "/" );
    req.setServletPath( "/welcome.html" );
    hec = getHandler(hm, req);
    assertTrue( "Handler is correct bean" , hec !=  null  && hec.getHandler() == bean);
 
    req =  new  MockHttpServletRequest( "GET" "/welcome.html" );
    req.setContextPath( "/app" );
    hec = getHandler(hm, req);
    assertTrue( "Handler is correct bean" , hec !=  null  && hec.getHandler() == bean);
 
    req =  new  MockHttpServletRequest( "GET" "/show.html" );
    hec = getHandler(hm, req);
    assertTrue( "Handler is correct bean" , hec !=  null  && hec.getHandler() == bean);
 
    req =  new  MockHttpServletRequest( "GET" "/bookseats.html" );
    hec = getHandler(hm, req);
    assertTrue( "Handler is correct bean" , hec !=  null  && hec.getHandler() == bean);
 
    req =  new  MockHttpServletRequest( "GET" "/original-welcome.html" );
    req.setAttribute(WebUtils.INCLUDE_REQUEST_URI_ATTRIBUTE,  "/welcome.html" );
    hec = getHandler(hm, req);
    assertTrue( "Handler is correct bean" , hec !=  null  && hec.getHandler() == bean);
 
    req =  new  MockHttpServletRequest( "GET" "/original-show.html" );
    req.setAttribute(WebUtils.INCLUDE_REQUEST_URI_ATTRIBUTE,  "/show.html" );
    hec = getHandler(hm, req);
    assertTrue( "Handler is correct bean" , hec !=  null  && hec.getHandler() == bean);
 
    req =  new  MockHttpServletRequest( "GET" "/original-bookseats.html" );
    req.setAttribute(WebUtils.INCLUDE_REQUEST_URI_ATTRIBUTE,  "/bookseats.html" );
    hec = getHandler(hm, req);
    assertTrue( "Handler is correct bean" , hec !=  null  && hec.getHandler() == bean);
 
    req =  new  MockHttpServletRequest( "GET" "/" );
    hec = getHandler(hm, req); //返回rootHandler
    assertTrue( "Handler is correct bean" , hec !=  null  && hec.getHandler() == bean);
    assertEquals( "/" , req.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE));
 
    req =  new  MockHttpServletRequest( "GET" "/somePath" );
    hec = getHandler(hm, req); //返回defaultHandler
    assertTrue( "Handler is correct bean" , hec !=  null  && hec.getHandler() == defaultBean);
    assertEquals( "/somePath" , req.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE));
}

2.2 BeanNameUrlHandlerMapping

内省方式,将注册的handler的名称作为相关的url

配置文件

1
2
3
4
5
6
7
8
9
10
11
<? xml  version = "1.0"  encoding = "UTF-8" ?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd">
 
< beans >
 
    < bean  id = "handlerMapping"  class = "org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping" />
 
    < bean  id = "godCtrl"  name = "/ /mypath/welcome.html /mypath/show.html /mypath/bookseats.html /mypath/reservation.html /mypath/payment.html /mypath/confirmation.html /mypath/test*"
          class = "java.lang.Object" />
 
</ beans >

快照

wKioL1hBiKWgi4PfAABGPLhFI1U272.png

测试用例

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
36
37
38
private  void  doTestRequestsWithSubPaths(HandlerMapping hm)  throws  Exception {
    Object bean = wac.getBean( "godCtrl" );
 
    MockHttpServletRequest req =  new  MockHttpServletRequest( "GET" "/mypath/welcome.html" );
    HandlerExecutionChain hec = hm.getHandler(req);
    assertTrue( "Handler is correct bean" , hec !=  null  && hec.getHandler() == bean);
 
    req =  new  MockHttpServletRequest( "GET" "/myapp/mypath/welcome.html" );
    req.setContextPath( "/myapp" );
    hec = hm.getHandler(req);
    assertTrue( "Handler is correct bean" , hec !=  null  && hec.getHandler() == bean);
 
    req =  new  MockHttpServletRequest( "GET" "/myapp/mypath/welcome.html" );
    req.setContextPath( "/myapp" );
    req.setServletPath( "/mypath/welcome.html" );
    hec = hm.getHandler(req);
    assertTrue( "Handler is correct bean" , hec !=  null  && hec.getHandler() == bean);
 
    req =  new  MockHttpServletRequest( "GET" "/myapp/myservlet/mypath/welcome.html" );
    req.setContextPath( "/myapp" );
    req.setServletPath( "/myservlet" );
    hec = hm.getHandler(req);
    assertTrue( "Handler is correct bean" , hec !=  null  && hec.getHandler() == bean);
 
    req =  new  MockHttpServletRequest( "GET" "/myapp/myapp/mypath/welcome.html" );
    req.setContextPath( "/myapp" );
    req.setServletPath( "/myapp" );
    hec = hm.getHandler(req);
    assertTrue( "Handler is correct bean" , hec !=  null  && hec.getHandler() == bean);
 
    req =  new  MockHttpServletRequest( "GET" "/mypath/show.html" );
    hec = hm.getHandler(req);
    assertTrue( "Handler is correct bean" , hec !=  null  && hec.getHandler() == bean);
 
    req =  new  MockHttpServletRequest( "GET" "/mypath/bookseats.html" );
    hec = hm.getHandler(req);
    assertTrue( "Handler is correct bean" , hec !=  null  && hec.getHandler() == bean);
}

值得一提的是

1
2
//返回的lookupPath:会根据request.servletPath信息,对requestURI截取
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);

2.3 ControllerClassNameHandlerMapping

注册@Controller annotated beans,按照class names 简单转换为url

 配置XML

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
<? xml  version = "1.0"  encoding = "UTF-8" ?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd">
 
< beans >
 
    < bean  id = "index"  class = "org.springframework.web.servlet.mvc.mapping.Controller" />
 
    < bean  id = "welcome"  class = "org.springframework.web.servlet.mvc.mapping.WelcomeController" />
    < bean  id = "admin"  class = "org.springframework.web.servlet.mvc.mapping.AdminController" />
    < bean  id = "buy"  class = "org.springframework.web.servlet.mvc.mapping.BuyForm" />
    
    < bean  name = "/myFile"  class = "org.springframework.web.servlet.mvc.UrlFilenameViewController" />
    < bean  name = "/myFile2"  class = "org.springframework.web.servlet.mvc.UrlFilenameViewController" />
 
    < bean  id = "mapping"  class = "org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping" >
       <!--
       We have to revert the default exclude for "org.springframework.web.servlet.mvc",
       since our test controllers sit in this package.
       -->
       < property  name = "excludedPackages" >< list ></ list ></ property <!-- 配置bean 的名称 -->
       < property  name = "excludedClasses"  value = "org.springframework.web.servlet.mvc.UrlFilenameViewController" />
    </ bean >
 
    < bean  id = "mapping2"  class = "org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping" >
       <!--
       We have to revert the default exclude for "org.springframework.web.servlet.mvc",
       since our test controllers sit in this package.
       -->
       < property  name = "excludedPackages" >< list ></ list ></ property >
       < property  name = "excludedClasses"  value = "org.springframework.web.servlet.mvc.UrlFilenameViewController" />
       < property  name = "caseSensitive"  value = "true" />
       < property  name = "pathPrefix"  value = "/myapp" /> <!-- 可匹配/myapp/mvc/mapping/buyForm-->
       < property  name = "basePackage"  value = "org.springframework.web.servlet" />
    </ bean >
 
    < bean  id = "mapping3"  class = "org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping" >
       <!--
       We have to revert the default exclude for "org.springframework.web.servlet.mvc",
       since our test controllers sit in this package.
       -->
       < property  name = "excludedPackages" >< list ></ list ></ property >
       < property  name = "excludedClasses"  value = "org.springframework.web.servlet.mvc.UrlFilenameViewController" />
       < property  name = "caseSensitive"  value = "true" />
       < property  name = "pathPrefix"  value = "/myapp" /> <!--/myapp/welcome -->
       < property  name = "basePackage"  value = "org.springframework.web.servlet.mvc.mapping" />
    </ bean >
     
    < bean  id = "mapping4"  class = "org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping" >
       <!--
       We have to revert the default exclude for "org.springframework.web.servlet.mvc",
       since our test controllers sit in this package.
       -->
       < property  name = "excludedPackages" >< list ></ list ></ property >
       < property  name = "excludedClasses"  value = "org.springframework.web.servlet.mvc.UrlFilenameViewController" />
       < property  name = "pathPrefix"  value = "myapp/" />
       < property  name = "basePackage"  value = "" />
       <!--匹配/myapp/org/springframework/web/servlet/mvc/mapping/welcome -->
    </ bean >
 
</ beans >

basePackage:Set the base package to be used for generating path mappings, including all subpackages underneath this packages as path elements.

pathPrefix:Specify a prefix to prepend to the path generated from the controller name.


生成url规则具体见generatePathMappings方法。

2.4 ControllerBeanNameHandlerMapping

和ControllerClassNameHandlerMapping类似,可以理解为url的生成规则有差异而已。

总结

介绍了相关的类结构,不难发现HandlerMapping的主要职责

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

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

  3. 维护mapping关系

  4. url的匹配能力

接下来,会继续探讨HandlerMapping的另一个重要部分

拦截器和DefaultAnnotationHandlerMapping




本文转自 randy_shandong 51CTO博客,原文链接:http://blog.51cto.com/dba10g/1879040,如需转载请自行联系原作者

相关文章
|
容器
SpringMVC常见组件之HandlerExceptionResolver分析-2
SpringMVC常见组件之HandlerExceptionResolver分析-2
77 0
|
7月前
|
XML 存储 Java
SpringMVC常见组件之HandlerMapping分析
SpringMVC常见组件之HandlerMapping分析
174 0
|
7月前
|
XML 缓存 前端开发
SpringMVC常见组件之HandlerAdapter分析
SpringMVC常见组件之HandlerAdapter分析
99 0
|
算法 Java API
SpringMVC常见组件之HandlerMethodArgumentResolver解析-1
SpringMVC常见组件之HandlerMethodArgumentResolver解析-1
91 0
SpringMVC常见组件之HandlerMethodArgumentResolver解析-2
SpringMVC常见组件之HandlerMethodArgumentResolver解析-2
63 0
|
JSON 前端开发 Java
SpringMVC常见组件之HandlerExceptionResolver分析-1
SpringMVC常见组件之HandlerExceptionResolver分析-1
216 0
|
JSON 前端开发 Java
HandlerMethodArgumentResolver(四):自定参数解析器处理特定场景需求,介绍PropertyNamingStrategy的使用【享学Spring MVC】(下)
HandlerMethodArgumentResolver(四):自定参数解析器处理特定场景需求,介绍PropertyNamingStrategy的使用【享学Spring MVC】(下)
HandlerMethodArgumentResolver(四):自定参数解析器处理特定场景需求,介绍PropertyNamingStrategy的使用【享学Spring MVC】(下)
|
前端开发 Java 索引
Spring MVC Controller 方法参数 Map 的实现类是什么?
问题 题主问题描述如下: 在SpringBoot中,Controller的参数中有Map接口类型的,请问他的实现类是什么? 突发奇想,在SpringBoot中,Controller的参数中有Map接口类型的
412 0
Spring MVC Controller 方法参数 Map 的实现类是什么?
|
前端开发 Java Spring
SpringMVC拦截器实现原理
SpringMVC拦截器实现原理
177 0
SpringMVC拦截器实现原理
|
前端开发 NoSQL Java
HandlerMethodArgumentResolver(四):自定参数解析器处理特定场景需求,介绍PropertyNamingStrategy的使用【享学Spring MVC】(上)
HandlerMethodArgumentResolver(四):自定参数解析器处理特定场景需求,介绍PropertyNamingStrategy的使用【享学Spring MVC】(上)
HandlerMethodArgumentResolver(四):自定参数解析器处理特定场景需求,介绍PropertyNamingStrategy的使用【享学Spring MVC】(上)