SpringMVC 九大组件之 HandlerMapping 深入分析

架构 2023-07-05 17:29:38
485阅读

 

前边跟朋友们共享了 SpringMVC 一个大概的复位步骤及其要求的大概解决步骤,在要求处理方式中,牵涉到九大部件,分别是:

  1. HandlerMapping
  2. HandlerAdapter
  3. HandlerExceptionResolver
  4. ViewResolver
  5. RequestToViewNameTranslator
  6. LocaleResolver
  7. ThemeResolver
  8. MultipartResolver
  9. FlashMapManager

这种部件坚信朋友们在日常开发设计中或多或少都是有牵涉到,假如你对这种部件觉得生疏,能够在公众号后台回应 ssm,免费获取松哥的新手入门教程视频。

那麼下面的2~3篇文章内容,松哥想和大伙儿详细分析这九大部件,从使用方法到源代码,逐个剖析,今日大家就先讨论一下这九大部件中的第一个 HandlerMapping。

1.概述

HandlerMapping 称为CPU映射器,它的功效便是依据当今 request 寻找相匹配的 Handler 和 Interceptor,随后封裝成一个 HandlerExecutionChain 目标回到,大家看来下 HandlerMapping 插口:

 
  1. public interface HandlerMapping { 
  2.  String BEST_MATCHING_HANDLER_ATTRIBUTE = HandlerMapping.class.getName()   ".bestMatchingHandler"
  3.  @Deprecated 
  4.  String LOOKUP_PATH = HandlerMapping.class.getName()   ".lookupPath"
  5.  String PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE = HandlerMapping.class.getName()   ".pathWithinHandlerMapping"
  6.  String BEST_MATCHING_PATTERN_ATTRIBUTE = HandlerMapping.class.getName()   ".bestMatchingPattern"
  7.  String INTROSPECT_TYPE_LEVEL_MAPPING = HandlerMapping.class.getName()   ".introspectTypeLevelMapping"
  8.  String URI_TEMPLATE_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName()   ".uriTemplateVariables"
  9.  String MATRIX_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName()   ".matrixVariables"
  10.  String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = HandlerMapping.class.getName()   ".producibleMediaTypes"
  11.  default boolean usesPathPatterns() { 
  12.   return false
  13.  } 
  14.  @Nullable 
  15.  HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception; 

能够见到,除开一堆申明的变量定义外,实际上就一个必须完成的方式 getHandler,该方式 的传参便是大家所掌握到的 HandlerExecutionChain。

HandlerMapping 的承继关联以下:

这一承继关联尽管看见有点儿绕,实际上认真观察就两类:

  • AbstractHandlerMethodMapping
  • AbstractUrlHandlerMapping

别的的全是一些輔助插口。

AbstractHandlerMethodMapping 管理体系下的全是依据方式 名开展配对的,而 AbstractUrlHandlerMapping 管理体系下的全是依据 URL 途径开展配对的,这二者有一个一同的父类 AbstractHandlerMapping,下面大家就对这三个重要类开展深入分析。

2.AbstractHandlerMapping

AbstractHandlerMapping 完成了 HandlerMapping 插口,不论是根据 URL 开展配对或是根据方式 名开展配对,全是根据承继 AbstractHandlerMapping 来完成的,因此 AbstractHandlerMapping 所做的事儿实际上便是一些公共性的事儿,将以一些必须实际解决的事儿则交到派生类去解决,这实际上便是典型性的免费模板方式 方式。

AbstractHandlerMapping 间接性承继自 ApplicationObjectSupport,并调用了 initApplicationContext 方式 (实际上该方式 也是一个免费模板方式 ),这也是 AbstractHandlerMapping 的复位通道方式 ,大家一起来看下:

 
  1. @Override 
  2. protected void initApplicationContext() throws BeansException { 
  3.  extendInterceptors(this.interceptors); 
  4.  detectMappedInterceptors(this.adaptedInterceptors); 
  5.  initInterceptors(); 

三个方式 都和拦截器相关。

extendInterceptors

 
  1. protected void extendInterceptors(List<Object> interceptors) { 

extendInterceptors 是一个免费模板方式 ,能够在派生类中完成,派生类完成了该方式 以后,能够对拦截器开展加上、删掉或是改动,但是在 SpringMVC 的实际完成中,实际上这一方式 并沒有在派生类中开展完成。

detectMappedInterceptors

 
  1. protected void detectMappedInterceptors(List<HandlerInterceptor> mappedInterceptors) { 
  2.  mappedInterceptors.addAll(BeanFactoryUtils.beansOfTypeIncludingAncestors( 
  3.    obtainApplicationContext(), MappedInterceptor.class, truefalse).values()); 

detectMappedInterceptors 方式 会从 SpringMVC 器皿及其 Spring 器皿中搜索全部 MappedInterceptor 种类的 Bean,搜索到以后加上到 mappedInterceptors 特性中(实际上便是全局性的 adaptedInterceptors 特性)。一般来说,大家界定好一个拦截器以后,也要在 XML 文档中配备该拦截器,拦截器及其各种各样配备信息内容,最后便会被封裝成一个 MappedInterceptor 目标。

initInterceptors

 
  1. protected void initInterceptors() { 
  2.  if (!this.interceptors.isEmpty()) { 
  3.   for (int i = 0; i < this.interceptors.size(); i ) { 
  4.    Object interceptor = this.interceptors.get(i); 
  5.    if (interceptor == null) { 
  6.     throw new IllegalArgumentException("Entry number "   i   " in interceptors array is null"); 
  7.    } 
  8.    this.adaptedInterceptors.add(adaptInterceptor(interceptor)); 
  9.   } 
  10.  } 

initInterceptors 方式 主要是开展拦截器的复位实际操作,主要内容是将 interceptors 结合中的拦截器加上到 adaptedInterceptors 结合中。

到此,大家见到,全部拦截器最后都是会被存进 adaptedInterceptors 自变量中。

AbstractHandlerMapping 的复位实际上也就是拦截器的复位全过程。

为何 AbstractHandlerMapping 中对拦截器这般高度重视呢?实际上并不是高度重视,大伙儿想一想,AbstractUrlHandlerMapping 和 AbstractHandlerMethodMapping 较大 的差别取决于搜索CPU的差别,一旦CPU找到,再去找拦截器,可是拦截器全是统一的,并没什么显著差别,因此 拦截器就统一在 AbstractHandlerMapping 中开展解决,而不容易去 AbstractUrlHandlerMapping 或是 AbstractHandlerMethodMapping 中解决。

下面大家再讨论一下 AbstractHandlerMapping#getHandler 方式 ,看一下CPU是怎样获得到的:

 
  1. @Override 
  2. @Nullable 
  3. public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { 
  4.  Object handler = getHandlerInternal(request); 
  5.  if (handler == null) { 
  6.   handler = getDefaultHandler(); 
  7.  } 
  8.  if (handler == null) { 
  9.   return null
  10.  } 
  11.  // Bean name or resolved handler? 
  12.  if (handler instanceof String) { 
  13.   String handlerName = (String) handler; 
  14.   handler = obtainApplicationContext().getBean(handlerName); 
  15.  } 
  16.  // Ensure presence of cached lookupPath for interceptors and others 
  17.  if (!ServletRequestPathUtils.hasCachedPath(request)) { 
  18.   initLookupPath(request); 
  19.  } 
  20.  HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request); 
  21.  if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) { 
  22.   CorsConfiguration config = getCorsConfiguration(handler, request); 
  23.   if (getCorsConfigurationSource() != null) { 
  24.    CorsConfiguration globalConfig = getCorsConfigurationSource().getCorsConfiguration(request); 
  25.    config = (globalConfig != null ? globalConfig.combine(config) : config); 
  26.   } 
  27.   if (config != null) { 
  28.    config.validateAllowCredentials(); 
  29.   } 
  30.   executionChain = getCorsHandlerExecutionChain(request, executionChain, config); 
  31.  } 
  32.  return executionChain; 

这一方式 的实行步骤是那样的:

  1. 最先启用 getHandlerInternal 方式 去试着获得CPU,getHandlerInternal 方式 也是一个免费模板方式 ,该方式 将在派生类中完成。
  2. 假如没找到相对的CPU,则启用 getDefaultHandler 方式 获得默认设置的CPU,我们在配备 HandlerMapping 的情况下能够配备默认设置的CPU。
  3. 假如寻找的CPU是一个字符串数组,则依据该字符串数组找去 SpringMVC 器皿中寻找相匹配的 Bean。
  4. 保证 lookupPath 存有,一会找相匹配的拦截器的情况下会采用。
  5. 寻找 handler 以后,下面再启用 getHandlerExecutionChain 方式 获得 HandlerExecutionChain 目标。
  6. 下面 if 里面的是开展跨域解决的,获得到跨域的有关配备,随后开展认证&配备,查验是不是容许跨域。跨域这方面的配备及其校检或是蛮有意思的,松哥之后专业发表文章来和朋友们细聊。

下面大家再讨论一下第五步的 getHandlerExecutionChain 方式 的实行逻辑性,恰好是在这个方式 里面把 handler 变成了 HandlerExecutionChain:

 
  1. protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) { 
  2.  HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ? 
  3.    (HandlerExecutionChain) handler : new HandlerExecutionChain(handler)); 
  4.  for (HandlerInterceptor interceptor : this.adaptedInterceptors) { 
  5.   if (interceptor instanceof MappedInterceptor) { 
  6.    MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor; 
  7.    if (mappedInterceptor.matches(request)) { 
  8.     chain.addInterceptor(mappedInterceptor.getInterceptor()); 
  9.    } 
  10.   } 
  11.   else { 
  12.    chain.addInterceptor(interceptor); 
  13.   } 
  14.  } 
  15.  return chain; 

这儿立即依据现有的 handler 建立一个新的 HandlerExecutionChain 目标,随后解析xml adaptedInterceptors 结合,该结合里储放的全是拦截器,假如拦截器的种类是 MappedInterceptor,则启用 matches 方式 去配对一下,看一下是不是阻拦当今要求的拦截器,如果是,则启用 chain.addInterceptor 方式 添加到 HandlerExecutionChain 目标中;假如便是一个一般拦截器,则立即添加到 HandlerExecutionChain 目标中。

这就是 AbstractHandlerMapping#getHandler 方式 的大概逻辑性,能够见到,这儿留了一个免费模板方式 getHandlerInternal 在派生类中完成,下面大家就讨论一下它的派生类。

3.AbstractUrlHandlerMapping

AbstractUrlHandlerMapping,看姓名就了解,全是依照 URL 详细地址来开展配对的,它的基本原理便是将 URL 详细地址与相匹配的 Handler 储存在同一个 Map 中,当启用 getHandlerInternal 方式 时,就依据要求的 URL 去 Map 中寻找相匹配的 Handler 回到就可以了。

这儿大家就先从他的 getHandlerInternal 方式 逐渐看起:

 
  1. @Override 
  2. @Nullable 
  3. protected Object getHandlerInternal(HttpServletRequest request) throws Exception { 
  4.  String lookupPath = initLookupPath(request); 
  5.  Object handler; 
  6.  if (usesPathPatterns()) { 
  7.   RequestPath path = ServletRequestPathUtils.getParsedRequestPath(request); 
  8.   handler = lookupHandler(path, lookupPath, request); 
  9.  } 
  10.  else { 
  11.   handler = lookupHandler(lookupPath, request); 
  12.  } 
  13.  if (handler == null) { 
  14.   // We need to care for the default handler directly, since we need to 
  15.   // expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well. 
  16.   Object rawHandler = null
  17.   if (StringUtils.matchesCharacter(lookupPath, '/')) { 
  18.    rawHandler = getRootHandler(); 
  19.   } 
  20.   if (rawHandler == null) { 
  21.    rawHandler = getDefaultHandler(); 
  22.   } 
  23.   if (rawHandler != null) { 
  24.    // Bean name or resolved handler? 
  25.    if (rawHandler instanceof String) { 
  26.     String handlerName = (String) rawHandler; 
  27.     rawHandler = obtainApplicationContext().getBean(handlerName); 
  28.    } 
  29.    validateHandler(rawHandler, request); 
  30.    handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null); 
  31.   } 
  32.  } 
  33.  return handler; 
  1. 最先寻找 lookupPath,便是要求的途径。这一方式 自身松哥就很少讲了,以前在Spring5 里面的新游戏玩法!这类 URL 要求要我涨眼界了!一文中经历详细介绍。
  2. 下面便是启用 lookupHandler 方式 获得 Handler 目标,lookupHandler 有一个轻载方式 ,实际用哪一个,关键看所应用的 URL 模式匹配,假如应用了全新的 PathPattern(Spring5 以后的),则应用三个主要参数的 lookupHandler;假如或是应用以前旧的 AntPathMatcher,则这儿应用2个主要参数的 lookupHandler。
  3. 假如前边沒有获得到 handler 案例,则下面再做各种各样试着,去各自搜索 RootHandler、DefaultHandler 等,假如寻找的 Handler 是一个 String,则去 Spring 器皿中搜索该 String 相匹配的 Bean,再启用 validateHandler 方式 来校检寻找的 handler 和 request 是不是配对,但是这是一个空方式 ,派生类都没有完成,因此 能够忽视之。最终再根据 buildPathExposingHandler 方式 给寻找的 handler 加上一些主要参数。

这就是全部 getHandlerInternal 方式 的逻辑性,事实上并不会太难,里面关键牵涉到 lookupHandler 和 buildPathExposingHandler 2个方式 ,必须和大伙儿详解下,大家各自看来。

lookupHandler

lookupHandler 有两个,大家各自看来。

 
  1. @Nullable 
  2. protected Object lookupHandler(String lookupPath, HttpServletRequest request) throws Exception { 
  3.  Object handler = getDirectMatch(lookupPath, request); 
  4.  if (handler != null) { 
  5.   return handler; 
  6.  } 
  7.  // Pattern match? 
  8.  List<String> matchingPatterns = new ArrayList<>(); 
  9.  for (String registeredPattern : this.handlerMap.keySet()) { 
  10.   if (getPathMatcher().match(registeredPattern, lookupPath)) { 
  11.    matchingPatterns.add(registeredPattern); 
  12.   } 
  13.   else if (useTrailingSlashMatch()) { 
  14.    if (!registeredPattern.endsWith("/") && getPathMatcher().match(registeredPattern   "/", lookupPath)) { 
  15.     matchingPatterns.add(registeredPattern   "/"); 
  16.    } 
  17.   } 
  18.  } 
  19.  String bestMatch = null
  20.  Comparator<String> patternComparator = getPathMatcher().getPatternComparator(lookupPath); 
  21.  if (!matchingPatterns.isEmpty()) { 
  22.   matchingPatterns.sort(patternComparator); 
  23.   bestMatch = matchingPatterns.get(0); 
  24.  } 
  25.  if (bestMatch != null) { 
  26.   handler = this.handlerMap.get(bestMatch); 
  27.   if (handler == null) { 
  28.    if (bestMatch.endsWith("/")) { 
  29.     handler = this.handlerMap.get(bestMatch.substring(0, bestMatch.length() - 1)); 
  30.    } 
  31.    if (handler == null) { 
  32.     throw new IllegalStateException( 
  33.       "Could not find handler for best pattern match ["   bestMatch   "]"); 
  34.    } 
  35.   } 
  36.   // Bean name or resolved handler? 
  37.   if (handler instanceof String) { 
  38.    String handlerName = (String) handler; 
  39.    handler = obtainApplicationContext().getBean(handlerName); 
  40.   } 
  41.   validateHandler(handler, request); 
  42.   String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestMatch, lookupPath); 
  43.   // There might be multiple 'best patterns', let's make sure we have the correct URI template variables 
  44.   // for all of them 
  45.   Map<String, String> uriTemplateVariables = new LinkedHashMap<>(); 
  46.   for (String matchingPattern : matchingPatterns) { 
  47.    if (patternComparator.compare(bestMatch, matchingPattern) == 0) { 
  48.     Map<String, String> vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, lookupPath); 
  49.     Map<String, String> decodedVars = getUrlPathHelper().decodePathVariables(request, vars); 
  50.     uriTemplateVariables.putAll(decodedVars); 
  51.    } 
  52.   } 
  53.   return buildPathExposingHandler(handler, bestMatch, pathWithinMapping, uriTemplateVariables); 
  54.  } 
  55.  // No handler found... 
  56.  return null
  57. @Nullable 
  58. private Object getDirectMatch(String urlPath, HttpServletRequest request) throws Exception { 
  59.  Object handler = this.handlerMap.get(urlPath); 
  60.  if (handler != null) { 
  61.   // Bean name or resolved handler? 
  62.   if (handler instanceof String) { 
  63.    String handlerName = (String) handler; 
  64.    handler = obtainApplicationContext().getBean(handlerName); 
  65.   } 
  66.   validateHandler(handler, request); 
  67.   return buildPathExposingHandler(handler, urlPath, urlPath, null); 
  68.  } 
  69.  return null

1.这儿最先启用 getDirectMatch 方式 立即去 handlerMap 中找相匹配的CPU,handlerMap 中就储存了要求 URL 和CPU的投射关联,实际的搜索全过程便是先去 handlerMap 中找,找到,如果是 String,则去 Spring 器皿中找相匹配的 Bean,随后启用 validateHandler 方式 去认证(事实上沒有认证,前边早已讲了),最终启用 buildPathExposingHandler 方式 加上拦截器。

2.假如 getDirectMatch 方式 传参不以 null,则立即将搜索到的 handler 回到,方式 到这里。那麼什么情况 getDirectMatch 方式 的传参不以 null 呢?简易来收便是沒有使用通配符的状况下,要求详细地址中沒有使用通配符,一个要求详细地址相匹配一个CPU,仅有这类状况,getDirectMatch 方式 传参才不以 null,由于 handlerMap 中储存的是编码的界定,例如大家界定编码的情况下,某一CPU的浏览途径很有可能含有使用通配符,可是在我们真实进行要求的情况下,要求途径里是沒有使用通配符的,这个时候再去 handlerMap 中就找不对相匹配的CPU了。假如采用了界定插口时采用了使用通配符,则必须在下面的编码中再次解决。

3.下面解决使用通配符的状况。最先界定 matchingPatterns 结合,将当今要求途径和 handlerMap 结合中储存的要求途径标准开展比照,但凡能配对上的标准都立即存进 matchingPatterns 结合中。实际解决中,还有一个 useTrailingSlashMatch 的很有可能,有的小伙伴们 SpringMVC 用的不娴熟,见到这儿很有可能就愣住,这儿是那样的,SpringMVC 中,默认设置是能够配对末尾 / 的,举个简易事例,假如你界定的插口是/user,那麼要求途径能够是 /user 还可以 /user/,这二种默认设置全是适用的,因此 这儿的 useTrailingSlashMatch 支系主要是解决后边这类状况,处理方法非常简单,就在 registeredPattern 后边再加上 / 随后再次和要求途径开展配对。

4.因为一个要求 URL 很有可能会和界定的好几个插口配对上,因此 matchingPatterns 自变量是一个二维数组,下面就需要对 matchingPatterns 开展排列,排列进行后,选择排序后的第一项做为最好选择项取值给 bestMatch 自变量。默认设置的排列标准是 AntPatternComparator,自然开发人员还可以自定。AntPatternComparator 中界定的优先以下:

路由器配备 优先
没有一切特殊字符的途径,如:配备路由器/a/b/c 第一优先
含有{}的途径,如:/a/{b}/c 第二优先
含有正则表达式的途径,如:/a/{regex:\d{3}}/c 第三优先
含有*的途径,如:/a/b/* 第四优先
含有**的途径,如:/a/b/** 第五优先
最模糊不清的配对:/** 最低优先级队列

5.寻找 bestMatch 以后,下面再依据 bestMatch 去 handlerMap 中寻找相匹配的CPU,立即找假如没找到,就要查验 bestMatch 是不是以 / 末尾,如果是以 / 末尾,则除掉末尾的 / 再去 handlerMap 中搜索,假如还没找到,那么就该抛出现异常出来。假如寻找的 handler 是 String 种类的,则再去 Spring 器皿中搜索相匹配的 Bean,下面再启用 validateHandler 方式 开展认证。

6.下面启用 extractPathWithinPattern 方式 获取出投射途径,比如界定的插口标准是 myroot/*.html,要求途径是 myroot/myfile.html,那麼最后获得到的便是myfile.html。

7.下面的 for 循环系统是为了更好地解决存有好几个最好配对标准的状况,在第四步中,大家对 matchingPatterns 开展排列,排列进行后,挑选第一项做为最好选择项取值给 bestMatch,可是最好选择项很有可能会出现好几个,这儿便是解决最好选择项有好几个的状况。

8.最终启用 buildPathExposingHandler 方式 申请注册2个內部拦截器,该方式 下面我能给大伙儿详解。

lookupHandler 还有一个轻载方式 ,但是只需大伙儿把这个方式 的实行步骤弄清楚了,轻载方式 实际上非常好了解,这儿松哥就不会再过多阐释了,唯一说起的便是轻载方式 用了 PathPattern 去配对 URL 途径,而这一方式 用了 AntPathMatcher 去配对 URL 途径。

buildPathExposingHandler

 
  1. protected Object buildPathExposingHandler(Object rawHandler, String bestMatchingPattern, 
  2.   String pathWithinMapping, @Nullable Map<String, String> uriTemplateVariables) { 
  3.  HandlerExecutionChain chain = new HandlerExecutionChain(rawHandler); 
  4.  chain.addInterceptor(new PathExposingHandlerInterceptor(bestMatchingPattern, pathWithinMapping)); 
  5.  if (!CollectionUtils.isEmpty(uriTemplateVariables)) { 
  6.   chain.addInterceptor(new UriTemplateVariablesHandlerInterceptor(uriTemplateVariables)); 
  7.  } 
  8.  return chain; 

buildPathExposingHandler 方式 向 HandlerExecutionChain 中加上了2个拦截器 PathExposingHandlerInterceptor 和 UriTemplateVariablesHandlerInterceptor,这两个拦截器在分别的 preHandle 中各自向 request 目标加上了一些特性,实际加上的特性朋友们能够自主查询,这一非常简单,我不多讲了。

在前面的方式 中,牵涉到一个关键的自变量 handlerMap,大家界定的插口和CPU中间的关联都储存在这个自变量中,那麼这一自变量是怎么复位的呢?这就牵涉到 AbstractUrlHandlerMapping 中的另一个方式 registerHandler:

 
  1. protected void registerHandler(String[] urlPaths, String beanName) throws BeansException, IllegalStateException { 
  2.  for (String urlPath : urlPaths) { 
  3.   registerHandler(urlPath, beanName); 
  4.  } 
  5. protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException { 
  6.  Object resolvedHandler = handler; 
  7.  if (!this.lazyInitHandlers && handler instanceof String) { 
  8.   String handlerName = (String) handler; 
  9.   ApplicationContext applicationContext = obtainApplicationContext(); 
  10.   if (applicationContext.isSingleton(handlerName)) { 
  11.    resolvedHandler = applicationContext.getBean(handlerName); 
  12.   } 
  13.  } 
  14.  Object mappedHandler = this.handlerMap.get(urlPath); 
  15.  if (mappedHandler != null) { 
  16.   if (mappedHandler != resolvedHandler) { 
  17.    throw new IllegalStateException( 
  18.      "Cannot map "   getHandlerDescription(handler)   " to URL path ["   urlPath   
  19.      "]: There is already "   getHandlerDescription(mappedHandler)   " mapped."); 
  20.   } 
  21.  } 
  22.  else { 
  23.   if (urlPath.equals("/")) { 
  24.    setRootHandler(resolvedHandler); 
  25.   } 
  26.   else if (urlPath.equals("/*")) { 
  27.    setDefaultHandler(resolvedHandler); 
  28.   } 
  29.   else { 
  30.    this.handlerMap.put(urlPath, resolvedHandler); 
  31.    if (getPatternParser() != null) { 
  32.     this.pathPatternHandlerMap.put(getPatternParser().parse(urlPath), resolvedHandler); 
  33.    } 
  34.   } 
  35.  } 

registerHandler(String[],String) 方式 有两个主要参数,第一个便是界定的要求途径,第二个主要参数则是CPU Bean 的姓名,第一个主要参数是一个二维数组,那是由于同一个CPU能够相匹配好几个不一样的要求途径。

在轻载方式 registerHandler(String,String) 里面,完成了 handlerMap 的复位,实际步骤以下:

  1. 要是没有设定 lazyInitHandlers,而且 handler 是 String 种类,那麼就要 Spring 器皿中寻找相匹配的 Bean 取值给 resolvedHandler。
  2. 依据 urlPath 去 handlerMap 中查询是不是早已有相匹配的CPU了,如果有得话,则抛出异常,一个 URL 详细地址只有相匹配一个CPU,这一非常好了解。
  3. 下面依据 URL 途径,将CPU开展配备,最后加上到 handlerMap 自变量中。

这就是 AbstractUrlHandlerMapping 的关键工作中,在其中 registerHandler 将在它的派生类中启用。

下面大家看来 AbstractUrlHandlerMapping 的派生类。

3.1 SimpleUrlHandlerMapping

为了更好地便捷解决,SimpleUrlHandlerMapping 中自身界定了一个 urlMap 自变量,那样能够在申请注册以前做一些预备处理,比如保证 全部的 URL 全是以 / 逐渐。SimpleUrlHandlerMapping 在界定时调用了父类的 initApplicationContext 方式 ,并在该方式 中启用了 registerHandlers,在 registerHandlers 中又启用了父类的 registerHandler 方式 完成了 handlerMap 的复位实际操作:

 
  1. @Override 
  2. public void initApplicationContext() throws BeansException { 
  3.  super.initApplicationContext(); 
  4.  registerHandlers(this.urlMap); 
  5. protected void registerHandlers(Map<String, Object> urlMap) throws BeansException { 
  6.  if (urlMap.isEmpty()) { 
  7.   logger.trace("No patterns in "   formatMappingName()); 
  8.  } 
  9.  else { 
  10.   urlMap.forEach((url, handler) -> { 
  11.    // Prepend with slash if not already present. 
  12.    if (!url.startsWith("/")) { 
  13.     url = "/"   url; 
  14.    } 
  15.    // Remove whitespace from handler bean name
  16.    if (handler instanceof String) { 
  17.     handler = ((String) handler).trim(); 
  18.    } 
  19.    registerHandler(url, handler); 
  20.   }); 
  21.  } 

这方面编码非常简单,确实没啥好说的,假如 URL 并不是以 / 开始,则手动式给它再加上/ 就可以。有朋友们很有可能要问了,urlMap 的值从哪里来?自然是以大家的环境变量里面来呀,像下边那样:

 
  1. <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"
  2.     <property name="urlMap"
  3.         <map> 
  4.             <entry key="/aaa" value-ref="/hello"/> 
  5.         </map> 
  6.     </property> 
  7. </bean> 

3.2 AbstractDetectingUrlHandlerMapping

AbstractDetectingUrlHandlerMapping 也是 AbstractUrlHandlerMapping 的派生类,可是它和 SimpleUrlHandlerMapping 有一些不一样的地区。

不一样的是哪儿呢?

AbstractDetectingUrlHandlerMapping 会全自动搜索到 SpringMVC 器皿及其 Spring 器皿中的全部 beanName,随后依据 beanName 分析出相匹配的 URL 详细地址,再将分析出的 url 详细地址和相匹配的 beanName 申请注册到父类的 handlerMap 自变量中。也就是说,假如你用了 AbstractDetectingUrlHandlerMapping,就无需像 SimpleUrlHandlerMapping 那般去逐个配备 URL 详细地址和CPU的投射关联了。大家看来下 AbstractDetectingUrlHandlerMapping#initApplicationContext 方式 :

 
  1. @Override 
  2. public void initApplicationContext() throws ApplicationContextException { 
  3.  super.initApplicationContext(); 
  4.  detectHandlers(); 
  5. protected void detectHandlers() throws BeansException { 
  6.  ApplicationContext applicationContext = obtainApplicationContext(); 
  7.  String[] beanNames = (this.detectHandlersInAncestorContexts ? 
  8.    BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, Object.class) : 
  9.    applicationContext.getBeanNamesForType(Object.class)); 
  10.  for (String beanName : beanNames) { 
  11.   String[] urls = determineUrlsForHandler(beanName); 
  12.   if (!ObjectUtils.isEmpty(urls)) { 
  13.    registerHandler(urls, beanName); 
  14.   } 
  15.  } 

AbstractDetectingUrlHandlerMapping 调用了父类的 initApplicationContext 方式 ,并在该方式 中启用了 detectHandlers 方式 ,在 detectHandlers 中,最先搜索到全部的 beanName,随后启用 determineUrlsForHandler 方式 剖析出 beanName 相匹配的 URL,但是这儿的 determineUrlsForHandler 方式 是一个空方式 ,实际的完成在它的派生类中,AbstractDetectingUrlHandlerMapping 只有一个派生类 BeanNameUrlHandlerMapping,大家一起来看下:

 
  1. public class BeanNameUrlHandlerMapping extends AbstractDetectingUrlHandlerMapping { 
  2.  @Override 
  3.  protected String[] determineUrlsForHandler(String beanName) { 
  4.   List<String> urls = new ArrayList<>(); 
  5.   if (beanName.startsWith("/")) { 
  6.    urls.add(beanName); 
  7.   } 
  8.   String[] aliases = obtainApplicationContext().getAliases(beanName); 
  9.   for (String alias : aliases) { 
  10.    if (alias.startsWith("/")) { 
  11.     urls.add(alias); 
  12.    } 
  13.   } 
  14.   return StringUtils.toStringArray(urls); 
  15.  } 
  16.  

这一类非常简单,里面就一个 determineUrlsForHandler 方式 ,这一方式 的实行逻辑性也非常简单,就分辨 beanName 是否以 / 逐渐,如果是,则将之做为 URL。

如果我们要想在新项目中应用 BeanNameUrlHandlerMapping,配备方法以下:

 
  1. <bean class="org.javaboy.init.HelloController" name="/hello"/> 
  2. <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping" id="handlerMapping"
  3. </bean> 

留意,Controller 的 name 务必是以 / 逐渐,不然该 bean 不容易被全自动做为CPU。

到此,AbstractUrlHandlerMapping 管理体系下的物品就和大伙儿共享完后。

4.AbstractHandlerMethodMapping

AbstractHandlerMethodMapping 管理体系下仅有三个类,分别是 AbstractHandlerMethodMapping、RequestMappingInfoHandlerMapping 及其 RequestMappingHandlerMapping,如下图:

在前面第三小标题的 AbstractUrlHandlerMapping 管理体系下,一个 Handler 一般便是一个类,可是在 AbstractHandlerMethodMapping 管理体系下,一个 Handler 便是一个 Mehtod,这也是大家现阶段应用 SpringMVC 时最普遍的使用方法,即立即用 @RequestMapping 去标识一个方式 ,该方式 便是一个 Handler。

下面大家就一起来看一下 AbstractHandlerMethodMapping。

4.1 复位步骤

AbstractHandlerMethodMapping 类完成了 InitializingBean 插口,因此 Spring 器皿会全自动启用其 afterPropertiesSet 方式 ,在这儿将进行复位实际操作:

 
  1. @Override 
  2. public void afterPropertiesSet() { 
  3.  initHandlerMethods(); 
  4. protected void initHandlerMethods() { 
  5.  for (String beanName : getCandidateBeanNames()) { 
  6.   if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) { 
  7.    processCandidateBean(beanName); 
  8.   } 
  9.  } 
  10.  handlerMethodsInitialized(getHandlerMethods()); 
  11. protected String[] getCandidateBeanNames() { 
  12.  return (this.detectHandlerMethodsInAncestorContexts ? 
  13.    BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) : 
  14.    obtainApplicationContext().getBeanNamesForType(Object.class)); 
  15. protected void processCandidateBean(String beanName) { 
  16.  Class<?> beanType = null
  17.  try { 
  18.   beanType = obtainApplicationContext().getType(beanName); 
  19.  } 
  20.  catch (Throwable ex) { 
  21.  } 
  22.  if (beanType != null && isHandler(beanType)) { 
  23.   detectHandlerMethods(beanName); 
  24.  } 

能够见到,实际的复位也是在 initHandlerMethods 方式 中进行的,在该方式 中,最先启用 getCandidateBeanNames 方式 获得器皿中全部的 beanName,随后启用 processCandidateBean 方式 对这种备选的 beanName 开展解决,实际的解决构思便是依据 beanName 寻找 beanType,随后启用 isHandler 方式 分辨该 beanType 是否一个 Handler,isHandler 是一个空方式 ,在它的派生类 RequestMappingHandlerMapping 中被完成了,该方式 主要是查验该 beanType 上是否有 @Controller 或是 @RequestMapping 注释,如果有,表明这就是我们想要的 handler,下面再启用 detectHandlerMethods 方式 储存 URL 和 handler 的投射关联:

 
  1. protected void detectHandlerMethods(Object handler) { 
  2.  Class<?> handlerType = (handler instanceof String ? 
  3.    obtainApplicationContext().getType((String) handler) : handler.getClass()); 
  4.  if (handlerType != null) { 
  5.   Class<?> userType = ClassUtils.getUserClass(handlerType); 
  6.   Map<Method, T> methods = MethodIntrospector.selectMethods(userType, 
  7.     (MethodIntrospector.MetadataLookup<T>) method -> { 
  8.      try { 
  9.       return getMappingForMethod(method, userType); 
  10.      } 
  11.      catch (Throwable ex) { 
  12.       throw new IllegalStateException("Invalid mapping on handler class ["   
  13.         userType.getName()   "]: "   method, ex); 
  14.      } 
  15.     }); 
  16.   methods.forEach((method, mapping) -> { 
  17.    Method invocableMethod = AopUtils.selectInvocableMethod(method, userType); 
  18.    registerHandlerMethod(handler, invocableMethod, mapping); 
  19.   }); 
  20.  } 
  1. 最先寻找 handler 的种类 handlerType。
  2. 启用 ClassUtils.getUserClass 方式 查验是不是 cglib 代理商的子目标种类,如果是,则回到父种类,不然将主要参数立即回到。
  3. 下面启用 MethodIntrospector.selectMethods 方式 获得当今 bean 中全部符合规定的 method。
  4. 解析xml methods,启用 registerHandlerMethod 方式 进行申请注册。

上边这一段编码里又牵涉到2个方式 :

  • getMappingForMethod
  • registerHandlerMethod

大家各自看来:

getMappingForMethod

getMappingForMethod 是一个免费模板方式 ,实际的完成也是在派生类 RequestMappingHandlerMapping 里面:

 
  1. @Override 
  2. @Nullable 
  3. protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) { 
  4.  RequestMappingInfo info = createRequestMappingInfo(method); 
  5.  if (info != null) { 
  6.   RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType); 
  7.   if (typeInfo != null) { 
  8.    info = typeInfo.combine(info); 
  9.   } 
  10.   String prefix = getPathPrefix(handlerType); 
  11.   if (prefix != null) { 
  12.    info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info); 
  13.   } 
  14.  } 
  15.  return info; 

最先依据 method 目标,启用 createRequestMappingInfo 方式 获得一个 RequestMappingInfo,一个 RequestMappingInfo 包括了一个接口标准的详细资料,比如主要参数、header、produces、consumes、要求方式 这些信息内容都在这儿边。下面再依据 handlerType 也获得一个 RequestMappingInfo,并启用 combine 方式 将2个 RequestMappingInfo 开展合拼。下面启用 getPathPrefix 方式 查询 handlerType 上是否有 URL 作为前缀,如果有,就加上到 info 里面去,最终将 info 回到。

这儿说起一下 handlerType 里面的这一作为前缀是那边来的,我们可以在 Controller 上应用 @RequestMapping 注释,配备一个途径作为前缀,那样 Controller 中的全部方式 都再加上了该途径作为前缀,可是这类方法必须一个一个的配备,假如想一次性配备全部的 Controller 呢?我们可以应用 Spring5.1 中澳引进的方式 addPathPrefix 来配备,以下:

 
  1. @Configuration 
  2. public class WebConfig implements WebMvcConfigurer { 
  3.  
  4.     @Override 
  5.     public void configurePathMatch(PathMatchConfigurer configurer) { 
  6.         configurer.setPatternParser(new PathPatternParser()).addPathPrefix("/itboyhub", HandlerTypePredicate.forAnnotation(RestController.class)); 
  7.     } 

上边这一配备表明,全部的 @RestController 标识的类都全自动再加上 itboyhub作为前缀。拥有这一配备以后,上边的 getPathPrefix 方式 获得到的便是/itboyhub 了。

registerHandlerMethod

当找全了 URL 和 handlerMethod 以后,下面便是将这种信息内容储存出来,方法以下:

 
  1. protected void registerHandlerMethod(Object handler, Method method, T mapping) { 
  2.  this.mappingRegistry.register(mapping, handler, method); 
  3. public void register(T mapping, Object handler, Method method) { 
  4.  this.readWriteLock.writeLock().lock(); 
  5.  try { 
  6.   HandlerMethod handlerMethod = createHandlerMethod(handler, method); 
  7.   validateMethodMapping(handlerMethod, mapping); 
  8.   Set<String> directPaths = AbstractHandlerMethodMapping.this.getDirectPaths(mapping); 
  9.   for (String path : directPaths) { 
  10.    this.pathLookup.add(path, mapping); 
  11.   } 
  12.   String name = null
  13.   if (getNamingStrategy() != null) { 
  14.    name = getNamingStrategy().getName(handlerMethod, mapping); 
  15.    addMappingName(name, handlerMethod); 
  16.   } 
  17.   CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping); 
  18.   if (corsConfig != null) { 
  19.    corsConfig.validateAllowCredentials(); 
  20.    this.corsLookup.put(handlerMethod, corsConfig); 
  21.   } 
  22.   this.registry.put(mapping, 
  23.     new MappingRegistration<>(mapping, handlerMethod, directPaths, name, corsConfig != null)); 
  24.  } 
  25.  finally { 
  26.   this.readWriteLock.writeLock().unlock(); 
  27.  } 
  1. 最先启用 createHandlerMethod 方式 建立 HandlerMethod 目标。
  2. 启用 validateMethodMapping 方式 对 handlerMethod 开展认证,主要是认证 handlerMethod 是不是早已存有。
  3. 从 mappings 中获取出 directPaths,便是不包含使用通配符的要求途径,随后将要求途径和 mapping 的投射关联储存到 pathLookup 中。
  4. 寻找全部 handler 的通称,启用 addMappingName 方式 加上到 nameLookup 中。比如我们在 HelloController 中界定了一个名叫 hello 的要求插口,那麼这儿取得的便是 HC#hello,HC 是 HelloController 中的英文大写字母。
  5. 复位跨域配备,并加上到 corsLookup 中。
  6. 将搭建好的关联加上到 registry 中。

多讲一句,第四步这个东西有啥用呢?这一实际上是 Spring4 中逐渐提升的作用,算作一个黑洞特效吧,尽管日常开发设计非常少用,可是我这里或是和大伙说一下。

倘若您有以下一个插口:

 
  1. @RestController 
  2. @RequestMapping("/javaboy"
  3. public class HelloController { 
  4.     @GetMapping("/aaa"
  5.     public String hello99() { 
  6.         return "aaa"
  7.     } 

如果你要求该插口的情况下,不愿根据途径,想立即根据方式 名,可不可以呢?当然可以!

在 jsp 文档中,加上以下网页链接:

 
  1. <%@ taglib prefix="s" uri="http://www.springframework.org/tags" %> 
  2. <%@ page contentType="text/html;charset=UTF-8" language="java" %> 
  3. <html> 
  4. <head> 
  5.     <title>Title</title> 
  6. </head> 
  7. <body> 
  8. <a href="${s:mvcUrl('HC#hello99').build()}">Go!</a> 
  9. </body> 
  10. </html> 

当这一 jsp 网页页面3D渲染进行后,href 特性就全自动变成 hello99 方式 的要求途径了。这一作用的完成,就取决于前边第四步的內容。

到此,大家就把 AbstractHandlerMethodMapping 的复位步骤看完了。

4.2 要求解决

下面大家看来下当要求来临后,AbstractHandlerMethodMapping 会如何处理。

和前边第三小标题一样,这儿解决要求的通道方式 也是 getHandlerInternal,以下:

 
  1. @Override 
  2. protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception { 
  3.  String lookupPath = initLookupPath(request); 
  4.  this.mappingRegistry.acquireReadLock(); 
  5.  try { 
  6.   HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request); 
  7.   return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null); 
  8.  } 
  9.  finally { 
  10.   this.mappingRegistry.releaseReadLock(); 
  11.  } 
  12. protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception { 
  13.  List<Match> matches = new ArrayList<>(); 
  14.  List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath); 
  15.  if (directPathMatches != null) { 
  16.   addMatchingMappings(directPathMatches, matches, request); 
  17.  } 
  18.  if (matches.isEmpty()) { 
  19.   addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request); 
  20.  } 
  21.  if (!matches.isEmpty()) { 
  22.   Match bestMatch = matches.get(0); 
  23.   if (matches.size() > 1) { 
  24.    Comparator<Match> comparator = new MatchComparator(getMappingComparator(request)); 
  25.    matches.sort(comparator); 
  26.    bestMatch = matches.get(0); 
  27.    if (CorsUtils.isPreFlightRequest(request)) { 
  28.     for (Match match : matches) { 
  29.      if (match.hasCorsConfig()) { 
  30.       return PREFLIGHT_AMBIGUOUS_MATCH; 
  31.      } 
  32.     } 
  33.    } 
  34.    else { 
  35.     Match secondBestMatch = matches.get(1); 
  36.     if (comparator.compare(bestMatch, secondBestMatch) == 0) { 
  37.      Method m1 = bestMatch.getHandlerMethod().getMethod(); 
  38.      Method m2 = secondBestMatch.getHandlerMethod().getMethod(); 
  39.      String uri = request.getRequestURI(); 
  40.      throw new IllegalStateException( 
  41.        "Ambiguous handler methods mapped for '"   uri   "': {"   m1   ", "   m2   "}"); 
  42.     } 
  43.    } 
  44.   } 
  45.   request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.getHandlerMethod()); 
  46.   handleMatch(bestMatch.mapping, lookupPath, request); 
  47.   return bestMatch.getHandlerMethod(); 
  48.  } 
  49.  else { 
  50.   return handleNoMatch(this.mappingRegistry.getRegistrations().keySet(), lookupPath, request); 
  51.  } 

这儿就较为非常容易,根据 lookupHandlerMethod 寻找相匹配的 HandlerMethod 回到就可以,假如 lookupHandlerMethod 方式 传参不以 null,则根据 createWithResolvedBean 建立 HandlerMethod(主要是确定里面的 Bean 等),实际的建立全过程松哥在后面的文章内容中会专业和大伙儿共享。lookupHandlerMethod 方式 也较为非常容易:

  1. 最先依据 lookupPath 寻找配对标准 directPathMatches,随后将获得到的配对标准加上到 matches 中(不包含使用通配符的要求走这儿)。
  2. 假如 matches 为空,表明依据 lookupPath 沒有寻找配对标准,那麼立即将全部配对标准添加 matches 中(包括使用通配符的要求走这儿)。
  3. 对 matches 开展排列,并选择排序后的第一个为最好配对项,假如前2个排列同样,则抛出异常。
  4. 大概的步骤就这样,实际到要求并沒有牵涉到它的派生类。

5.总结

SpringMVC 九大部件,今日和朋友们把 HandlerMapping 过去了一遍,实际上只需用心看,这儿并沒有难题。假如朋友们感觉阅读文章费劲,还可以在公众号后台回应 ssm,查询松哥视频录制的完全免费基础教程~

the end
免责声明:本文不代表本站的观点和立场,如有侵权请联系本站删除!本站仅提供信息存储空间服务。