前面跟小伙伴们分享了 SpringMVC 一个大体的初始化流程以及请求的大体处理流程,在请求处理过程当中,涉及到九大组件,分别是:html
这些组件相信小伙伴们在平常开发中多多少少都有涉及到,若是你对这些组件感到陌生,能够在公众号后台回复 ssm,免费获取松哥的入门视频教程。java
那么接下来的几篇文章,松哥想和你们深刻分析这九大组件,从用法到源码,挨个分析,今天咱们就先来看看这九大组件中的第一个 HandlerMapping。web
HandlerMapping 叫作处理器映射器,它的做用就是根据当前 request 找到对应的 Handler 和 Interceptor,而后封装成一个 HandlerExecutionChain 对象返回,咱们来看下 HandlerMapping 接口:spring
public interface HandlerMapping { String BEST_MATCHING_HANDLER_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingHandler"; @Deprecated String LOOKUP_PATH = HandlerMapping.class.getName() + ".lookupPath"; String PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE = HandlerMapping.class.getName() + ".pathWithinHandlerMapping"; String BEST_MATCHING_PATTERN_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingPattern"; String INTROSPECT_TYPE_LEVEL_MAPPING = HandlerMapping.class.getName() + ".introspectTypeLevelMapping"; String URI_TEMPLATE_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".uriTemplateVariables"; String MATRIX_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".matrixVariables"; String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = HandlerMapping.class.getName() + ".producibleMediaTypes"; default boolean usesPathPatterns() { return false; } @Nullable HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception; }
能够看到,除了一堆声明的常量外,其实就一个须要实现的方法 getHandler,该方法的返回值就是咱们所了解到的 HandlerExecutionChain。跨域
HandlerMapping 的继承关系以下:数组
这个继承关系虽然看着有点绕,其实仔细观察就两大类:mvc
其余的都是一些辅助接口。app
AbstractHandlerMethodMapping 体系下的都是根据方法名进行匹配的,而 AbstractUrlHandlerMapping 体系下的都是根据 URL 路径进行匹配的,这二者有一个共同的父类 AbstractHandlerMapping,接下来咱们就对这三个关键类进行详细分析。cors
AbstractHandlerMapping 实现了 HandlerMapping 接口,不管是经过 URL 进行匹配仍是经过方法名进行匹配,都是经过继承 AbstractHandlerMapping 来实现的,因此 AbstractHandlerMapping 所作的事情其实就是一些公共的事情,将以一些须要具体处理的事情则交给子类去处理,这其实就是典型的模版方法模式。jsp
AbstractHandlerMapping 间接继承自 ApplicationObjectSupport,并重写了 initApplicationContext 方法(其实该方法也是一个模版方法),这也是 AbstractHandlerMapping 的初始化入口方法,咱们一块儿来看下:
@Override protected void initApplicationContext() throws BeansException { extendInterceptors(this.interceptors); detectMappedInterceptors(this.adaptedInterceptors); initInterceptors(); }
三个方法都和拦截器有关。
extendInterceptors
protected void extendInterceptors(List<Object> interceptors) { }
extendInterceptors 是一个模版方法,能够在子类中实现,子类实现了该方法以后,能够对拦截器进行添加、删除或者修改,不过在 SpringMVC 的具体实现中,其实这个方法并无在子类中进行实现。
detectMappedInterceptors
protected void detectMappedInterceptors(List<HandlerInterceptor> mappedInterceptors) { mappedInterceptors.addAll(BeanFactoryUtils.beansOfTypeIncludingAncestors( obtainApplicationContext(), MappedInterceptor.class, true, false).values()); }
detectMappedInterceptors 方法会从 SpringMVC 容器以及 Spring 容器中查找全部 MappedInterceptor 类型的 Bean,查找到以后添加到 mappedInterceptors 属性中(其实就是全局的 adaptedInterceptors 属性)。通常来讲,咱们定义好一个拦截器以后,还要在 XML 文件中配置该拦截器,拦截器以及各类配置信息,最终就会被封装成一个 MappedInterceptor 对象。
initInterceptors
protected void initInterceptors() { if (!this.interceptors.isEmpty()) { 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.add(adaptInterceptor(interceptor)); } } }
initInterceptors 方法主要是进行拦截器的初始化操做,具体内容是将 interceptors 集合中的拦截器添加到 adaptedInterceptors 集合中。
至此,咱们看到,全部拦截器最终都会被存入 adaptedInterceptors 变量中。
AbstractHandlerMapping 的初始化其实也就是拦截器的初始化过程。
为何 AbstractHandlerMapping 中对拦截器如此重视呢?其实不是重视,你们想一想,AbstractUrlHandlerMapping 和 AbstractHandlerMethodMapping 最大的区别在于查找处理器的区别,一旦处理器找到了,再去找拦截器,可是拦截器都是统一的,并无什么明显区别,因此拦截器就统一在 AbstractHandlerMapping 中进行处理,而不会去 AbstractUrlHandlerMapping 或者 AbstractHandlerMethodMapping 中处理。
接下来咱们再来看看 AbstractHandlerMapping#getHandler 方法,看看处理器是如何获取到的:
@Override @Nullable public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { Object handler = getHandlerInternal(request); if (handler == null) { handler = getDefaultHandler(); } if (handler == null) { return null; } // Bean name or resolved handler? if (handler instanceof String) { String handlerName = (String) handler; handler = obtainApplicationContext().getBean(handlerName); } // Ensure presence of cached lookupPath for interceptors and others if (!ServletRequestPathUtils.hasCachedPath(request)) { initLookupPath(request); } HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request); if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) { CorsConfiguration config = getCorsConfiguration(handler, request); if (getCorsConfigurationSource() != null) { CorsConfiguration globalConfig = getCorsConfigurationSource().getCorsConfiguration(request); config = (globalConfig != null ? globalConfig.combine(config) : config); } if (config != null) { config.validateAllowCredentials(); } executionChain = getCorsHandlerExecutionChain(request, executionChain, config); } return executionChain; }
这个方法的执行流程是这样的:
接下来咱们再来看看第五步的 getHandlerExecutionChain 方法的执行逻辑,正是在这个方法里边把 handler 变成了 HandlerExecutionChain:
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) { HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ? (HandlerExecutionChain) handler : new HandlerExecutionChain(handler)); for (HandlerInterceptor interceptor : this.adaptedInterceptors) { if (interceptor instanceof MappedInterceptor) { MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor; if (mappedInterceptor.matches(request)) { chain.addInterceptor(mappedInterceptor.getInterceptor()); } } else { chain.addInterceptor(interceptor); } } return chain; }
这里直接根据已有的 handler 建立一个新的 HandlerExecutionChain 对象,而后遍历 adaptedInterceptors 集合,该集合里存放的都是拦截器,若是拦截器的类型是 MappedInterceptor,则调用 matches 方法去匹配一下,看一下是不是拦截当前请求的拦截器,若是是,则调用 chain.addInterceptor 方法加入到 HandlerExecutionChain 对象中;若是就是一个普通拦截器,则直接加入到 HandlerExecutionChain 对象中。
这就是 AbstractHandlerMapping#getHandler 方法的大体逻辑,能够看到,这里留了一个模版方法 getHandlerInternal 在子类中实现,接下来咱们就来看看它的子类。
AbstractUrlHandlerMapping,看名字就知道,都是按照 URL 地址来进行匹配的,它的原理就是将 URL 地址与对应的 Handler 保存在同一个 Map 中,当调用 getHandlerInternal 方法时,就根据请求的 URL 去 Map 中找到对应的 Handler 返回就好了。
这里咱们就先从他的 getHandlerInternal 方法开始看起:
@Override @Nullable protected Object getHandlerInternal(HttpServletRequest request) throws Exception { String lookupPath = initLookupPath(request); Object handler; if (usesPathPatterns()) { RequestPath path = ServletRequestPathUtils.getParsedRequestPath(request); handler = lookupHandler(path, lookupPath, request); } else { handler = lookupHandler(lookupPath, request); } if (handler == null) { // We need to care for the default handler directly, since we need to // expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well. Object rawHandler = null; if (StringUtils.matchesCharacter(lookupPath, '/')) { rawHandler = getRootHandler(); } if (rawHandler == null) { rawHandler = getDefaultHandler(); } if (rawHandler != null) { // Bean name or resolved handler? if (rawHandler instanceof String) { String handlerName = (String) rawHandler; rawHandler = obtainApplicationContext().getBean(handlerName); } validateHandler(rawHandler, request); handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null); } } return handler; }
这就是整个 getHandlerInternal 方法的逻辑,实际上并不难,里边主要涉及到 lookupHandler 和 buildPathExposingHandler 两个方法,须要和你们详细介绍下,咱们分别来看。
lookupHandler
lookupHandler 有两个,咱们分别来看。
@Nullable protected Object lookupHandler(String lookupPath, HttpServletRequest request) throws Exception { Object handler = getDirectMatch(lookupPath, request); if (handler != null) { return handler; } // Pattern match? List<String> matchingPatterns = new ArrayList<>(); for (String registeredPattern : this.handlerMap.keySet()) { if (getPathMatcher().match(registeredPattern, lookupPath)) { matchingPatterns.add(registeredPattern); } else if (useTrailingSlashMatch()) { if (!registeredPattern.endsWith("/") && getPathMatcher().match(registeredPattern + "/", lookupPath)) { matchingPatterns.add(registeredPattern + "/"); } } } String bestMatch = null; Comparator<String> patternComparator = getPathMatcher().getPatternComparator(lookupPath); if (!matchingPatterns.isEmpty()) { matchingPatterns.sort(patternComparator); bestMatch = matchingPatterns.get(0); } if (bestMatch != null) { handler = this.handlerMap.get(bestMatch); if (handler == null) { if (bestMatch.endsWith("/")) { handler = this.handlerMap.get(bestMatch.substring(0, bestMatch.length() - 1)); } if (handler == null) { throw new IllegalStateException( "Could not find handler for best pattern match [" + bestMatch + "]"); } } // Bean name or resolved handler? if (handler instanceof String) { String handlerName = (String) handler; handler = obtainApplicationContext().getBean(handlerName); } validateHandler(handler, request); String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestMatch, lookupPath); // There might be multiple 'best patterns', let's make sure we have the correct URI template variables // for all of them Map<String, String> uriTemplateVariables = new LinkedHashMap<>(); for (String matchingPattern : matchingPatterns) { if (patternComparator.compare(bestMatch, matchingPattern) == 0) { Map<String, String> vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, lookupPath); Map<String, String> decodedVars = getUrlPathHelper().decodePathVariables(request, vars); uriTemplateVariables.putAll(decodedVars); } } return buildPathExposingHandler(handler, bestMatch, pathWithinMapping, uriTemplateVariables); } // No handler found... return null; } @Nullable private Object getDirectMatch(String urlPath, HttpServletRequest request) throws Exception { Object handler = this.handlerMap.get(urlPath); if (handler != null) { // Bean name or resolved handler? if (handler instanceof String) { String handlerName = (String) handler; handler = obtainApplicationContext().getBean(handlerName); } validateHandler(handler, request); return buildPathExposingHandler(handler, urlPath, urlPath, null); } return null; }
/
的,举个简单例子,若是你定义的接口是 /user
,那么请求路径能够是 /user
也能够 /user/
,这两种默认都是支持的,因此这里的 useTrailingSlashMatch 分支主要是处理后面这种状况,处理方式很简单,就在 registeredPattern 后面加上 /
而后继续和请求路径进行匹配。路由配置 | 优先级 |
---|---|
不含任何特殊符号的路径,如:配置路由/a/b/c |
第一优先级 |
带有{} 的路径,如:/a/{b}/c |
第二优先级 |
带有正则的路径,如:/a/{regex:\d{3}}/c |
第三优先级 |
带有* 的路径,如:/a/b/* |
第四优先级 |
带有** 的路径,如:/a/b/** |
第五优先级 |
最模糊的匹配:/** |
最低优先级 |
/
结尾,若是是以 /
结尾,则去掉结尾的 /
再去 handlerMap 中查找,若是还没找到,那就该抛异常出来了。若是找到的 handler 是 String 类型的,则再去 Spring 容器中查找对应的 Bean,接下来再调用 validateHandler 方法进行验证。myroot/*.html
,请求路径是 myroot/myfile.html
,那么最终获取到的就是 myfile.html
。lookupHandler 还有一个重载方法,不过只要你们把这个方法的执行流程搞清楚了,重载方法其实很好理解,这里松哥就再也不赘述了,惟一要说的就是重载方法用了 PathPattern 去匹配 URL 路径,而这个方法用了 AntPathMatcher 去匹配 URL 路径。
buildPathExposingHandler
protected Object buildPathExposingHandler(Object rawHandler, String bestMatchingPattern, String pathWithinMapping, @Nullable Map<String, String> uriTemplateVariables) { HandlerExecutionChain chain = new HandlerExecutionChain(rawHandler); chain.addInterceptor(new PathExposingHandlerInterceptor(bestMatchingPattern, pathWithinMapping)); if (!CollectionUtils.isEmpty(uriTemplateVariables)) { chain.addInterceptor(new UriTemplateVariablesHandlerInterceptor(uriTemplateVariables)); } return chain; }
buildPathExposingHandler 方法向 HandlerExecutionChain 中添加了两个拦截器 PathExposingHandlerInterceptor 和 UriTemplateVariablesHandlerInterceptor,这两个拦截器在各自的 preHandle 中分别向 request 对象添加了一些属性,具体添加的属性小伙伴们能够自行查看,这个比较简单,我就很少说了。
在前面的方法中,涉及到一个重要的变量 handlerMap,咱们定义的接口和处理器之间的关系都保存在这个变量中,那么这个变量是怎么初始化的呢?这就涉及到 AbstractUrlHandlerMapping 中的另外一个方法 registerHandler:
protected void registerHandler(String[] urlPaths, String beanName) throws BeansException, IllegalStateException { for (String urlPath : urlPaths) { registerHandler(urlPath, beanName); } } protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException { Object resolvedHandler = handler; if (!this.lazyInitHandlers && handler instanceof String) { String handlerName = (String) handler; ApplicationContext applicationContext = obtainApplicationContext(); if (applicationContext.isSingleton(handlerName)) { resolvedHandler = applicationContext.getBean(handlerName); } } Object mappedHandler = this.handlerMap.get(urlPath); if (mappedHandler != null) { if (mappedHandler != resolvedHandler) { throw new IllegalStateException( "Cannot map " + getHandlerDescription(handler) + " to URL path [" + urlPath + "]: There is already " + getHandlerDescription(mappedHandler) + " mapped."); } } else { if (urlPath.equals("/")) { setRootHandler(resolvedHandler); } else if (urlPath.equals("/*")) { setDefaultHandler(resolvedHandler); } else { this.handlerMap.put(urlPath, resolvedHandler); if (getPatternParser() != null) { this.pathPatternHandlerMap.put(getPatternParser().parse(urlPath), resolvedHandler); } } } }
registerHandler(String[],String) 方法有两个参数,第一个就是定义的请求路径,第二个参数则是处理器 Bean 的名字,第一个参数是一个数组,那是由于同一个处理器能够对应多个不一样的请求路径。
在重载方法 registerHandler(String,String) 里边,完成了 handlerMap 的初始化,具体流程以下:
这就是 AbstractUrlHandlerMapping 的主要工做,其中 registerHandler 将在它的子类中调用。
接下来咱们来看 AbstractUrlHandlerMapping 的子类。
为了方便处理,SimpleUrlHandlerMapping 中本身定义了一个 urlMap 变量,这样能够在注册以前作一些预处理,例如确保全部的 URL 都是以 /
开始。SimpleUrlHandlerMapping 在定义时重写了父类的 initApplicationContext 方法,并在该方法中调用了 registerHandlers,在 registerHandlers 中又调用了父类的 registerHandler 方法完成了 handlerMap 的初始化操做:
@Override public void initApplicationContext() throws BeansException { super.initApplicationContext(); registerHandlers(this.urlMap); } protected void registerHandlers(Map<String, Object> urlMap) throws BeansException { if (urlMap.isEmpty()) { logger.trace("No patterns in " + formatMappingName()); } else { urlMap.forEach((url, handler) -> { // Prepend with slash if not already present. if (!url.startsWith("/")) { url = "/" + url; } // Remove whitespace from handler bean name. if (handler instanceof String) { handler = ((String) handler).trim(); } registerHandler(url, handler); }); } }
这块代码很简单,实在没啥好说的,若是 URL 不是以 /
开头,则手动给它加上 /
便可。有小伙伴们可能要问了,urlMap 的值从哪里来?固然是从咱们的配置文件里边来呀,像下面这样:
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="urlMap"> <map> <entry key="/aaa" value-ref="/hello"/> </map> </property> </bean>
AbstractDetectingUrlHandlerMapping 也是 AbstractUrlHandlerMapping 的子类,可是它和 SimpleUrlHandlerMapping 有一些不同的地方。
不同的是哪里呢?
AbstractDetectingUrlHandlerMapping 会自动查找到 SpringMVC 容器以及 Spring 容器中的全部 beanName,而后根据 beanName 解析出对应的 URL 地址,再将解析出的 url 地址和对应的 beanName 注册到父类的 handlerMap 变量中。换句话说,若是你用了 AbstractDetectingUrlHandlerMapping,就不用像 SimpleUrlHandlerMapping 那样去挨个配置 URL 地址和处理器的映射关系了。咱们来看下 AbstractDetectingUrlHandlerMapping#initApplicationContext 方法:
@Override public void initApplicationContext() throws ApplicationContextException { super.initApplicationContext(); detectHandlers(); } protected void detectHandlers() throws BeansException { ApplicationContext applicationContext = obtainApplicationContext(); String[] beanNames = (this.detectHandlersInAncestorContexts ? BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, Object.class) : applicationContext.getBeanNamesForType(Object.class)); for (String beanName : beanNames) { String[] urls = determineUrlsForHandler(beanName); if (!ObjectUtils.isEmpty(urls)) { registerHandler(urls, beanName); } } }
AbstractDetectingUrlHandlerMapping 重写了父类的 initApplicationContext 方法,并在该方法中调用了 detectHandlers 方法,在 detectHandlers 中,首先查找到全部的 beanName,而后调用 determineUrlsForHandler 方法分析出 beanName 对应的 URL,不过这里的 determineUrlsForHandler 方法是一个空方法,具体的实如今它的子类中,AbstractDetectingUrlHandlerMapping 只有一个子类 BeanNameUrlHandlerMapping,咱们一块儿来看下:
public class BeanNameUrlHandlerMapping extends AbstractDetectingUrlHandlerMapping { @Override protected String[] determineUrlsForHandler(String beanName) { List<String> urls = new ArrayList<>(); if (beanName.startsWith("/")) { urls.add(beanName); } String[] aliases = obtainApplicationContext().getAliases(beanName); for (String alias : aliases) { if (alias.startsWith("/")) { urls.add(alias); } } return StringUtils.toStringArray(urls); } }
这个类很简单,里边就一个 determineUrlsForHandler 方法,这个方法的执行逻辑也很简单,就判断 beanName 是否是以 /
开始,若是是,则将之做为 URL。
若是咱们想要在项目中使用 BeanNameUrlHandlerMapping,配置方式以下:
<bean class="org.javaboy.init.HelloController" name="/hello"/> <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping" id="handlerMapping"> </bean>
注意,Controller 的 name 必须是以 /
开始,不然该 bean 不会被自动做为处理器。
至此,AbstractUrlHandlerMapping 体系下的东西就和你们分享完了。
AbstractHandlerMethodMapping 体系下只有三个类,分别是 AbstractHandlerMethodMapping、RequestMappingInfoHandlerMapping 以及 RequestMappingHandlerMapping,以下图:
在前面第三小节的 AbstractUrlHandlerMapping 体系下,一个 Handler 通常就是一个类,可是在 AbstractHandlerMethodMapping 体系下,一个 Handler 就是一个 Mehtod,这也是咱们目前使用 SpringMVC 时最多见的用法,即直接用 @RequestMapping 去标记一个方法,该方法就是一个 Handler。
接下来咱们就一块儿来看看 AbstractHandlerMethodMapping。
AbstractHandlerMethodMapping 类实现了 InitializingBean 接口,因此 Spring 容器会自动调用其 afterPropertiesSet 方法,在这里将完成初始化操做:
@Override public void afterPropertiesSet() { initHandlerMethods(); } protected void initHandlerMethods() { for (String beanName : getCandidateBeanNames()) { if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) { processCandidateBean(beanName); } } handlerMethodsInitialized(getHandlerMethods()); } protected String[] getCandidateBeanNames() { return (this.detectHandlerMethodsInAncestorContexts ? BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) : obtainApplicationContext().getBeanNamesForType(Object.class)); } protected void processCandidateBean(String beanName) { Class<?> beanType = null; try { beanType = obtainApplicationContext().getType(beanName); } catch (Throwable ex) { } if (beanType != null && isHandler(beanType)) { detectHandlerMethods(beanName); } }
能够看到,具体的初始化又是在 initHandlerMethods 方法中完成的,在该方法中,首先调用 getCandidateBeanNames 方法获取容器中全部的 beanName,而后调用 processCandidateBean 方法对这些候选的 beanName 进行处理,具体的处理思路就是根据 beanName 找到 beanType,而后调用 isHandler 方法判断该 beanType 是否是一个 Handler,isHandler 是一个空方法,在它的子类 RequestMappingHandlerMapping 中被实现了,该方法主要是检查该 beanType 上有没有 @Controller
或者 @RequestMapping
注解,若是有,说明这就是咱们想要的 handler,接下来再调用 detectHandlerMethods 方法保存 URL 和 handler 的映射关系:
protected void detectHandlerMethods(Object handler) { Class<?> handlerType = (handler instanceof String ? obtainApplicationContext().getType((String) handler) : handler.getClass()); if (handlerType != null) { Class<?> userType = ClassUtils.getUserClass(handlerType); Map<Method, T> methods = MethodIntrospector.selectMethods(userType, (MethodIntrospector.MetadataLookup<T>) method -> { try { return getMappingForMethod(method, userType); } catch (Throwable ex) { throw new IllegalStateException("Invalid mapping on handler class [" + userType.getName() + "]: " + method, ex); } }); methods.forEach((method, mapping) -> { Method invocableMethod = AopUtils.selectInvocableMethod(method, userType); registerHandlerMethod(handler, invocableMethod, mapping); }); } }
上面这段代码里又涉及到两个方法:
咱们分别来看:
getMappingForMethod
getMappingForMethod 是一个模版方法,具体的实现也是在子类 RequestMappingHandlerMapping 里边:
@Override @Nullable protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) { RequestMappingInfo info = createRequestMappingInfo(method); if (info != null) { RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType); if (typeInfo != null) { info = typeInfo.combine(info); } String prefix = getPathPrefix(handlerType); if (prefix != null) { info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info); } } return info; }
首先根据 method 对象,调用 createRequestMappingInfo 方法获取一个 RequestMappingInfo,一个 RequestMappingInfo 包含了一个接口定义的详细信息,例如参数、header、produces、consumes、请求方法等等信息都在这里边。接下来再根据 handlerType 也获取一个 RequestMappingInfo,并调用 combine 方法将两个 RequestMappingInfo 进行合并。接下来调用 getPathPrefix 方法查看 handlerType 上有没有 URL 前缀,若是有,就添加到 info 里边去,最后将 info 返回。
这里要说一下 handlerType 里边的这个前缀是那里来的,咱们能够在 Controller 上使用 @RequestMapping
注解,配置一个路径前缀,这样 Controller 中的全部方法都加上了该路径前缀,可是这种方式须要一个一个的配置,若是想一次性配置全部的 Controller 呢?咱们可使用 Spring5.1 中新引入的方法 addPathPrefix 来配置,以下:
@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void configurePathMatch(PathMatchConfigurer configurer) { configurer.setPatternParser(new PathPatternParser()).addPathPrefix("/itboyhub", HandlerTypePredicate.forAnnotation(RestController.class)); } }
上面这个配置表示,全部的 @RestController
标记的类都自动加上 itboyhub
前缀。有了这个配置以后,上面的 getPathPrefix 方法获取到的就是 /itboyhub
了。
registerHandlerMethod
当找齐了 URL 和 handlerMethod 以后,接下来就是将这些信息保存下来,方式以下:
protected void registerHandlerMethod(Object handler, Method method, T mapping) { this.mappingRegistry.register(mapping, handler, method); } public void register(T mapping, Object handler, Method method) { this.readWriteLock.writeLock().lock(); try { HandlerMethod handlerMethod = createHandlerMethod(handler, method); validateMethodMapping(handlerMethod, mapping); Set<String> directPaths = AbstractHandlerMethodMapping.this.getDirectPaths(mapping); for (String path : directPaths) { this.pathLookup.add(path, mapping); } String name = null; if (getNamingStrategy() != null) { name = getNamingStrategy().getName(handlerMethod, mapping); addMappingName(name, handlerMethod); } CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping); if (corsConfig != null) { corsConfig.validateAllowCredentials(); this.corsLookup.put(handlerMethod, corsConfig); } this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directPaths, name, corsConfig != null)); } finally { this.readWriteLock.writeLock().unlock(); } }
HC#hello
,HC 是 HelloController 中的大写字母。多说一句,第四步这个东西有啥用呢?这个实际上是 Spring4 中开始增长的功能,算是一个小彩蛋吧,虽然平常开发不多用,可是我这里仍是和你们说一下。
假如你有以下一个接口:
@RestController @RequestMapping("/javaboy") public class HelloController { @GetMapping("/aaa") public String hello99() { return "aaa"; } }
当你请求该接口的时候,不想经过路径,想直接经过方法名,行不行呢?固然能够!
在 jsp 文件中,添加以下超连接:
<%@ taglib prefix="s" uri="http://www.springframework.org/tags" %> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> <a href="${s:mvcUrl('HC#hello99').build()}">Go!</a> </body> </html>
当这个 jsp 页面渲染完成后,href 属性就自动成了 hello99 方法的请求路径了。这个功能的实现,就依赖于前面第四步的内容。
至此,咱们就把 AbstractHandlerMethodMapping 的初始化流程看完了。
接下来咱们来看下当请求到来后,AbstractHandlerMethodMapping 会如何处理。
和前面第三小节同样,这里处理请求的入口方法也是 getHandlerInternal,以下:
@Override protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception { String lookupPath = initLookupPath(request); this.mappingRegistry.acquireReadLock(); try { HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request); return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null); } finally { this.mappingRegistry.releaseReadLock(); } } protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception { List<Match> matches = new ArrayList<>(); List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath); if (directPathMatches != null) { addMatchingMappings(directPathMatches, matches, request); } if (matches.isEmpty()) { addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request); } if (!matches.isEmpty()) { Match bestMatch = matches.get(0); if (matches.size() > 1) { Comparator<Match> comparator = new MatchComparator(getMappingComparator(request)); matches.sort(comparator); bestMatch = matches.get(0); if (CorsUtils.isPreFlightRequest(request)) { for (Match match : matches) { if (match.hasCorsConfig()) { return PREFLIGHT_AMBIGUOUS_MATCH; } } } else { Match secondBestMatch = matches.get(1); if (comparator.compare(bestMatch, secondBestMatch) == 0) { Method m1 = bestMatch.getHandlerMethod().getMethod(); Method m2 = secondBestMatch.getHandlerMethod().getMethod(); String uri = request.getRequestURI(); throw new IllegalStateException( "Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}"); } } } request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.getHandlerMethod()); handleMatch(bestMatch.mapping, lookupPath, request); return bestMatch.getHandlerMethod(); } else { return handleNoMatch(this.mappingRegistry.getRegistrations().keySet(), lookupPath, request); } }
这里就比较容易,经过 lookupHandlerMethod 找到对应的 HandlerMethod 返回便可,若是 lookupHandlerMethod 方法返回值不为 null,则经过 createWithResolvedBean 建立 HandlerMethod(主要是确认里边的 Bean 等),具体的建立过程松哥在后面的文章中会专门和你们分享。lookupHandlerMethod 方法也比较容易:
大体的流程就是这样,具体到请求并无涉及到它的子类。
SpringMVC 九大组件,今天和小伙伴们把 HandlerMapping 过了一遍,其实只要认真看,这里并无难点。若是小伙伴们以为阅读吃力,也能够在公众号后台回复 ssm,查看松哥录制的免费入门教程~
剩下的八大组件源码解析,小伙伴们敬请期待!