初始化IOC容器
Spring初始化的时候会优先初始化自定义的类,下面这个就是
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#0
类的结构图

根据这个结构图可以发现,RequestMappingHandlerMapping这个功能还是非常强大的.毕竟注册路由这个功能还是需要依赖IOC容器的,所以它已经实现了ApplicationContextAware持有了上下文的对象.拥有了这个对象,就可以很方便的去容器中查找controller中的所有对象和方法了.

ioc 实例话这个类的时候 会经过一个 AbstractAutowireCapableBeanFactory - invokeInitMethods 的方法, 这个方法会判断这个类是否实现InitializingBean这个类的方法,如果是则调用它的afterPropertiesSet方法。

  1. protected void invokeInitMethods(String beanName, final Object bean, RootBeanDefinition mbd)
  2. throws Throwable {
  3.  
  4. boolean isInitializingBean = (bean instanceof InitializingBean);
  5. if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
  6. if (logger.isDebugEnabled()) {
  7. logger.debug("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");
  8. }
  9. if (System.getSecurityManager() != null) {
  10. try {
  11. AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
  12. @Override
  13. public Object run() throws Exception {
  14. ((InitializingBean) bean).afterPropertiesSet();
  15. return null;
  16. }
  17. }, getAccessControlContext());
  18. }
  19. catch (PrivilegedActionException pae) {
  20. throw pae.getException();
  21. }
  22. }
  23. else {
  24. // 调用这个初始化中接口的方法
  25. ((InitializingBean) bean).afterPropertiesSet();
  26. }
  27. }
  28. if (mbd != null) {
  29. String initMethodName = mbd.getInitMethodName();
  30. if (initMethodName != null && !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
  31. !mbd.isExternallyManagedInitMethod(initMethodName)) {
  32. invokeCustomInitMethod(beanName, bean, mbd);
  33. }
  34. }
  35. }

  

这时候RequestMappingHandlerMapping类会委托给父类去处理这个afterPropertiesSet方法。

获取MVC中的ioc容器里面注册的对象,并且进行循环遍历
判断isHandler注册的对象中的类是否包含@Controller或者@RequestMapping等注解,满足则会对类的方法进行遍历
进行注册到AbstractHandlerMethodMapping中的handlerMethods、urlMap中,一个是以方法做为key[RequestMappingInfo],一个是以url作为key进行存储
注册完毕之后,前端发送过来的请求就会从urlMap这里面进行查找
AbstractHandlerMethodMapping

  1. // 父类又会交给一个initHandlerMethods处理
  2. public void afterPropertiesSet() {
  3. initHandlerMethods();
  4. }
  5.  
  6. // 初始化方法处理
  7. protected void initHandlerMethods() {
  8. if (logger.isDebugEnabled()) {
  9. logger.debug("Looking for request mappings in application context: " + getApplicationContext());
  10. }
  11. // 查询父类是否有处理HandlerMethods方法的上下文,如果没有则获取MVC容器中的所有对象
  12. String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
  13. BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
  14. getApplicationContext().getBeanNamesForType(Object.class));
  15. // 遍历对象
  16. for (String beanName : beanNames) {
  17. // 类名前缀是否包含scopedTarget.
  18. // isHandler 这个方法很关键
  19. // 判断该类是否包含Controller或者RequestMapping注解
  20. if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX) &&
  21. isHandler(getApplicationContext().getType(beanName))){
  22. // 处理包含handle中的方法
  23. detectHandlerMethods(beanName);
  24. }
  25. }
  26. handlerMethodsInitialized(getHandlerMethods());
  27. }
  28.  
  29. // 注意这个类是在子类RequestMappingHandlerMapping中
  30. @Override
  31. protected boolean isHandler(Class<?> beanType) {
  32. return ((AnnotationUtils.findAnnotation(beanType, Controller.class) != null) ||
  33. (AnnotationUtils.findAnnotation(beanType, RequestMapping.class) != null));
  34. }
  35.  
  36. /**
  37. * Look for handler methods in a handler.
  38. * @param handler the bean name of a handler or a handler instance
  39. */
  40. protected void detectHandlerMethods(final Object handler) {
  41. Class<?> handlerType =
  42. (handler instanceof String ? getApplicationContext().getType((String) handler) : handler.getClass());
  43.  
  44. // Avoid repeated calls to getMappingForMethod which would rebuild RequestMappingInfo instances
  45. final Map<Method, T> mappings = new IdentityHashMap<Method, T>();
  46. final Class<?> userType = ClassUtils.getUserClass(handlerType);
  47. // 查找该类中的所有和handler相关的方法
  48. Set<Method> methods = HandlerMethodSelector.selectMethods(userType, new MethodFilter() {
  49. @Override
  50. public boolean matches(Method method) {
  51. T mapping = getMappingForMethod(method, userType);
  52. if (mapping != null) {
  53. mappings.put(method, mapping);
  54. return true;
  55. }
  56. else {
  57. return false;
  58. }
  59. }
  60. });
  61. // 把上面遍历出来的方法进行相应的注册
  62. for (Method method : methods) {
  63. registerHandlerMethod(handler, method, mappings.get(method));
  64. }
  65. }
  66.  
  67. // 这里是实际注册handler的方法,在AbstractHandlerMethodMapping中
  68. protected void registerHandlerMethod(Object handler, Method method, T mapping) {
  69. // 创建一个HandlerMethod对象,并且HandlerMethod这个对象持有整个工厂的引用
  70. HandlerMethod newHandlerMethod = createHandlerMethod(handler, method);
  71. // 判断是否存在重复的handler,如果存在则报错
  72. HandlerMethod oldHandlerMethod = this.handlerMethods.get(mapping);
  73. if (oldHandlerMethod != null && !oldHandlerMethod.equals(newHandlerMethod)) {
  74. throw new IllegalStateException("Ambiguous mapping found. Cannot map '" + newHandlerMethod.getBean() +
  75. "' bean method \n" + newHandlerMethod + "\nto " + mapping + ": There is already '" +
  76. oldHandlerMethod.getBean() + "' bean method\n" + oldHandlerMethod + " mapped.");
  77. }
  78. // 不存在则将这个handle注册到handlerMethods中, 这里是根据方法的标识作为key
  79. this.handlerMethods.put(mapping, newHandlerMethod);
  80. if (logger.isInfoEnabled()) {
  81. logger.info("Mapped \"" + mapping + "\" onto " + newHandlerMethod);
  82. }
  83. // 这里就是根据RequestMapping的value作为key进行存储 .. 也就url进行存储
  84. Set<String> patterns = getMappingPathPatterns(mapping);
  85. for (String pattern : patterns) {
  86. if (!getPathMatcher().isPattern(pattern)) {
  87. this.urlMap.add(pattern, mapping);
  88. }
  89. }
  90.  
  91. if (this.namingStrategy != null) {
  92. String name = this.namingStrategy.getName(newHandlerMethod, mapping);
  93. updateNameMap(name, newHandlerMethod);
  94. }
  95. }

  

调用过程
其实最终也会交给AbstractHandlerMethodMapping方法进行处理,因为上面注册的时候就已经把url注册到urlMap中了,不过有几点需要注意
1. 它的匹配规则
- 如果注册的时候是//urlPath , 但是你前端传递过来的时候是/urlPath, 这时候urlMap是匹配不到的,这时候它会从方法匹配中去查找,需要注意的是方法匹配需要遍历所有注册过的方法,相当于全局查找

  1. protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
  2. List<Match> matches = new ArrayList<Match>();
  3. // 从urlMap注册中查找对应匹配的handler
  4. List<T> directPathMatches = this.urlMap.get(lookupPath);
  5. if (directPathMatches != null) {
  6. addMatchingMappings(directPathMatches, matches, request);
  7. }
  8. // 如果查找不到,就从方法注册的map中进行遍历匹配
  9. if (matches.isEmpty()) {
  10. // No choice but to go through all mappings...
  11. // 从方法为key的map中查找就涉及到一个优先级匹配的规则了
  12. addMatchingMappings(this.handlerMethods.keySet(), matches, request);
  13. }
  14. // 如果取出来的匹配的url不为空,可能是1个或者多个的时候
  15. if (!matches.isEmpty()) {
  16. // 获取一个比较器 , 注意这里的比较器最终的实现规则是在AntPatternComparator 类中, 这是一个内部类 -> AntPathMatcher
  17. Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
  18. // 进行规则排序
  19. Collections.sort(matches, comparator);
  20. if (logger.isTraceEnabled()) {
  21. logger.trace("Found " + matches.size() + " matching mapping(s) for [" + lookupPath + "] : " + matches);
  22. }
  23. // 获取上面排序之后的第一个,也就是优先级最高的
  24. Match bestMatch = matches.get(0);
  25. if (matches.size() > 1) {
  26. // 如果存在多个,则会将第二个和第一个进行比较 , 如果得到的优先级规则是相等的,则抛异常
  27. Match secondBestMatch = matches.get(1);
  28. if (comparator.compare(bestMatch, secondBestMatch) == 0) {
  29. Method m1 = bestMatch.handlerMethod.getMethod();
  30. Method m2 = secondBestMatch.handlerMethod.getMethod();
  31. throw new IllegalStateException(
  32. "Ambiguous handler methods mapped for HTTP path '" + request.getRequestURL() + "': {" +
  33. m1 + ", " + m2 + "}");
  34. }
  35. }
  36. handleMatch(bestMatch.mapping, lookupPath, request);
  37. // 返回优先级最好的handler
  38. return bestMatch.handlerMethod;
  39. }
  40. else {
  41. return handleNoMatch(handlerMethods.keySet(), lookupPath, request);
  42. }
  43. }

  

这里会涉及到一个匹配规则,spring是如何做的呢?

addMatchingMappings这个里面会进行一个循环匹配所有URL
遍历的时候会得到这个key的RequestMappingInfo对象.RequestMappingInfo对象持有(PatternsRequestCondition对象)拥有匹配url的规则
PatternsRequestCondition对象又会交给AntPathMatcher , 这里的持有对象都是在初始化中如果没有指定Spring给你默认的,相当于实际匹配的规则都是在AntPathMatcher里面去操作的
AntPathMatcher操作思路:
1. 他定义了一个ConcurrentHashMap对象stringMatcherCache,key是url中的每一个/后面的对象

/a/b/{c} 它承装的就是三个对象 a、b、{c}都是它的key,value就是一个AntPathStringMatcher对象,这个对象会处理{c}这种情况,转化成对应的类似*这种正则匹配
2. 上层经过解析到达AntPathMatcher 对象时是url中的一段一段path,然后从stringMatcherCache去找有没有对应的AntPathStringMatcher,如果没有则实例化一个,然后根据这个进行match,匹配则返回true

整体的匹配思路:
前端传递一个: /a/b/c

程序会将这个url解析成3段去匹配 【a、b、c】
2.先拿a去AntPathMatcher的matchStrings方法去进行匹配,而AntPathMatcher会转交给AntPathStringMatcher,通过则返回true
这里会涉及到一个优先级的问题:
比如后端定义了3个url :
1. /a/b/{c}
2. /a/b/*
3. /a/b/**

这三个都满足上面的匹配条件,这时候Spring会要优先选取一个最好的handle去处理.
这里最终处理的是
AntPathMatcher的内部类AntPatternComparator实现了一个compare方法

  1. /**
  2. 优先级排序规则
  3. 1. 需要注意的是返回 1 的 表示正序 -1 表示倒序
  4.  
  5. */
  6. protected static class AntPatternComparator implements Comparator<String> {
  7. private final String path;
  8.  
  9. public AntPatternComparator(String path) {
  10. this.path = path;
  11. }
  12.  
  13. /**
  14. * Compare two patterns to determine which should match first, i.e. which
  15. * is the most specific regarding the current path.
  16. * @return a negative integer, zero, or a positive integer as pattern1 is
  17. * more specific, equally specific, or less specific than pattern2.
  18. */
  19. @Override
  20. public int compare(String pattern1, String pattern2) {
  21. PatternInfo info1 = new PatternInfo(pattern1);
  22. PatternInfo info2 = new PatternInfo(pattern2);
  23.  
  24. // 如果pattern1 > pattern2 则会将 pattern2 放在前面 (优先级较高), 反则不动
  25. // 再通俗一点讲 1表示Info2的优先级上调 , -1表示 info1的优先级上调
  26.  
  27. // 如果是参数(pattern)里面是null 或者 是 /**
  28. if (info1.isLeastSpecific() && info2.isLeastSpecific()) {
  29. return 0;
  30. }
  31. // 优先级降低
  32. else if (info1.isLeastSpecific()) {
  33. return 1;
  34. }
  35. // 优先级上升
  36. else if (info2.isLeastSpecific()) {
  37. return -1;
  38. }
  39. // 具体匹配
  40. boolean pattern1EqualsPath = pattern1.equals(path);
  41. boolean pattern2EqualsPath = pattern2.equals(path);
  42. if (pattern1EqualsPath && pattern2EqualsPath) {
  43. return 0;
  44. }
  45.  
  46. else if (pattern1EqualsPath) {
  47. return -1;
  48. }
  49. else if (pattern2EqualsPath) {
  50. return 1;
  51. }
  52. // 如果第一个前缀不是/**和后缀不是/**结尾
  53. if (info1.isPrefixPattern() && info2.getDoubleWildcards() == 0) {
  54. return 1;
  55. }
  56. else if (info2.isPrefixPattern() && info1.getDoubleWildcards() == 0) {
  57. return -1;
  58. }
  59. // TotalCount=(包含"{"的次数) +("*出现的次数") + "("**出现的次数")"
  60. if (info1.getTotalCount() != info2.getTotalCount()) {
  61. return info1.getTotalCount() - info2.getTotalCount();
  62. }
  63. //"\\{[^/]+?\\}"替换后的长度
  64. if (info1.getLength() != info2.getLength()) {
  65. return info2.getLength() - info1.getLength();
  66. }
  67. //*次数比较
  68. if (info1.getSingleWildcards() < info2.getSingleWildcards()) {
  69. return -1;
  70. }
  71. else if (info2.getSingleWildcards() < info1.getSingleWildcards()) {
  72. return 1;
  73. }
  74. // { 出现的次数
  75. if (info1.getUriVars() < info2.getUriVars()) {
  76. return -1;
  77. }
  78. else if (info2.getUriVars() < info1.getUriVars()) {
  79. return 1;
  80. }
  81. return 0;
  82. }
  83. }

  

根据上面定义的优先级进行排序.之后会将优先级也就是list中的下标为0的作为最好的handle进行处理

总结
初始化逻辑:
1. 在IOC容器已经初始化容器的时候,会加载Spring的一个内置对象RequestMappingHandlerMapping,而这个对象又实现了InitializingBean方法.这时候就会触发afterPropertiesSet方法的调用
2. 而这个方法里面则是会对ioc容器中的所有对象进行遍历,找到类中包含@Controller或者@RequestMapping等注解的类
3. 将上面符合的类的方法进行遍历并且注册到Map中,这个map又分为一个url为key的map和方法对象为key的map
4. 这时候HandlerMethod已经初始化完成

调用逻辑:
1. 当前端发送一个url请求的时候,会被springMvc拦截到
2. 会根据当前的url进行handler匹配,第一个匹配是根据url进行全路径匹配,这个匹配容器封装在了一个叫UrlMap中,如果匹配到直接返回map中的handlerMethod对象
3. 如果上面没有匹配到,这时候会从一个封装方法的Map中去进行正则匹配,这里匹配是将所有的Controller中的路由方法进行匹配,这里会先将url按照/进行拆分成多个String进行全路径匹配,匹配到直接返回。
4. 如果匹配不到,则开始进行一系列的正则匹配..具体的匹配规则可以参考上面的AntPatternComparator类,这里会将正则匹配到的url进行一个排序,最前面的优先级最高.
5. 这里就会将优先级最高的进行反射调用

转自:https://blog.csdn.net/lkx444368875/article/details/78949631

SpringMVC handleMapping映射过程的更多相关文章

  1. 【spring源码学习】springMVC之映射,拦截器解析,请求数据注入解析,DispatcherServlet执行过程

    [一]springMVC之url和bean映射原理和源码解析 映射基本过程 (1)springMVC配置映射,需要在xml配置文件中配置<mvc:annotation-driven >  ...

  2. SpringBoot对比SpringMVC,SpringMVC 处理请求过程

    (问较多:1.SpringBoot对比SpringMVC.2.SpringMVC 处理请求过程.问:springboot的理解 Spring,Spring MVC,Spring Boot 三者比较 S ...

  3. springMVC请求调用过程

    在传统的MVC模式中,Tomcat通过读取web.XML配置文件来获取servlet和访问路径的映射关系,这样在访问tomcat就能将请求转发给对应的servlet进行处理. 自定义的servlet是 ...

  4. springmvc组件组成以及springmvc的执行过程

    springmvc三大组件 处理器映射器:用户请求路径到Controller方法的映射 处理器适配器:根据handler(controlelr类)的开发方式(注解开发/其他开发) 方式的不同区寻找不同 ...

  5. Linux内存管理 (2)页表的映射过程

    专题:Linux内存管理专题 关键词:swapper_pd_dir.ARM PGD/PTE.Linux PGD/PTE.pgd_offset_k. Linux下的页表映射分为两种,一是Linux自身的 ...

  6. SpringMVC中映射路径的用法之请求限制、命名空间

    SpringMVC中映射路径的请求限制 什么是SpringMVC请求限制? 在SpringMVC中,支持对请求的设置.如果不满足限制条件的话,就不让请求访问执行方法,这样可以大大提高执行方法 的安全性 ...

  7. SpringMVC handleMapping 处理器映射器 属性清单

    映射器的属性清单 defaultHandler         在映射与所有处理器都不匹配的情况下,指定默认的处理器(处理器即你定义的Controller(action)类) order        ...

  8. SpringMVC核心——映射问题

    一.SpringMVC 使用 RequestMapping 来解决映射问题. 二.在学习 RequestMapping 之前,首先来看一张图. 这张图表示的是发送一次 http 请求时,所包含的请求 ...

  9. SpringMVC的映射器、适配器、解析器

    1.处理器和适配器 1.1springmvc的映射器 根据客户端请求的url,找到处理本次请求的handler(处理器),将url和controller关联起来 1.2springmvc的适配器 对映 ...

随机推荐

  1. 【Java每日一题】20170329

    20170328问题解析请点击今日问题下方的“[Java每日一题]20170329”查看(问题解析在公众号首发,公众号ID:weknow619) package Mar2017; public cla ...

  2. Elasticsearch系列(2):安装Elasticsearch(Linux环境)

    系统环境 操作系统:CentOS 6.9 Elasticsearch:6.2.2 Filebeat:6.2.2(收集IIS日志) Kibana:6.2.2 Java:Java 8 注意:elk最好选择 ...

  3. hihoCoder编程练习赛72

    题目1 : 玩具设计师 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 小Ho作为Z国知名玩具品牌AKIRE的首席设计师,对玩具零件的挑剔程度已经到了叹为观止的地步.所有 ...

  4. 如何用ABP框架快速完成项目(14) - 结尾? 当然不是, 这只是开始!

    此文当前版本号: 3 最近更新时间: 2018-12-9 04:52   本课程是方向性课程, 目的是避免南辕北辙. 方向盘一旦打正确, 还得需要以下文章去写好具体程序: 前面每篇文章里面的链接, 比 ...

  5. HTML5 & CSS3初学者指南(4) – Canvas使用

    介绍 传统的HTML主要用于文本的创建,可以通过<img>标签插入图像,动画的实现则需要第三方插件.在这方面,传统的HTML极其缺乏满足现代网页多媒体需求的能力.HTML5的到来,带来了新 ...

  6. Git 结合Git使用Bitbucket进行代码版本管理流程规范与实践

    结合Git使用Bitbucket进行代码版本管理流程规范与实践   By:授客 QQ:1033553122   目录 目录 1 一. 测试环境 2 二. 新建项目 2 三. 新建公有版本库 3 四. ...

  7. Python-Django 整合Django和jquery-easyui

    整合Django和jquery-easyui by:授客 QQ:1033553122 测试环境 win7 64 Python 3.4.0 jquery-easyui-1.5.1 下载地址1:http: ...

  8. 浅谈Kotlin(四):控制流

    浅谈Kotlin(一):简介及Android Studio中配置 浅谈Kotlin(二):基本类型.基本语法.代码风格 浅谈Kotlin(三):类 浅谈Kotlin(四):控制流 本篇介绍Kotlin ...

  9. Pycharm配置anaconda环境

    概述 在上节介绍了anaconda管理python环境,而Pycharm作为主流python IDE,两者配合使用才算完美. 配置 File - Setting - Project Interpret ...

  10. 关于iframe跨域实践

    提要 项目中与到iframe子页面中需要通过top获取在父页面中的全局变量的需求,由于App部署的缘故,导致父页面和iframe子页面分别在不同的端口下,导致iframe跨域现象,通过查阅资料进行问题 ...