在Spring MVC 3.1 之前的版本中,Spring默认使用 ​​DefaultAnnotationHandlerMapping​​​,​​AnnotationMethodHandlerAdapter​​来处理 ​@RequestMapping​​注解和请求方法调用,而从3.1开始提供了一组新的API,​​RequestMappingHandlerMapping​​​和​​RequestMappingHandlerAdapter​​完成这些工作。

HandlerMapping 叫做处理器映射器,它的作用就是根据当前 request 找到对应的 Handler 和 Interceptor,然后封装成一个 HandlerExecutionChain 对象返回

其提供了一个方法​​HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;​​供子类实现。 我们这里先看下关于请求映射处理的家族树图示:
SpringMVC常见组件之HandlerMapping分析_子类
从上图可以看到,请求映射的家族从该类主要分为三支:根据方法进行匹配的​​​AbstractHandlerMethodMapping​​​、​​RouterFunctionMapping​​​以及根据请求URL进行批的​​AbstractUrlHandlerMapping​​。其中RouterFunctionMapping是5.2版本后引入的,这里暂不分析。

【1】 AbstractHandlerMapping

AbstractHandlerMapping如下所示,其提供了系列模板方法以及抽象方法。

//  返回HandlerInterceptor数组
protected final HandlerInterceptor[] getAdaptedInterceptors() {}

// 返回 HandlerExecutionChain 其包括handler HandlerInterceptor
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {}

// 返回所有配置的MappedInterceptor
protected final MappedInterceptor[] getMappedInterceptors() {}

// 抽象方法供子类实现 ,返回一个handler
protected abstract Object getHandlerInternal(HttpServletRequest request) throws Exception;

​AbstractHandlerMapping​​​ 间接继承自 ​​ApplicationObjectSupport​​​,并重写了 ​​initApplicationContext​​​ 方法(其实该方法也是一个模版方法),这也是 ​​AbstractHandlerMapping​​​ 的初始化入口方法,该方法主要就是扫描容器中的拦截器然后放到全局的​​private final List<HandlerInterceptor> adaptedInterceptors = new ArrayList<>();​​变量中。

因为​​ApplicationObjectSupport​​​实现了​​ApplicationContextAware​​​接口,故而在bean的实例化过程中,凡是实现了​​ApplicationContextAware​​​接口的类都会被调用其​​setApplicationContext​​​方法。​​ApplicationObjectSupport​​​的​​setApplicationContext​​​又提供了​​initApplicationContext​​​方法供子类实现。也就是说​​ApplicationObjectSupport​​​的子类实例化时会被调用​​initApplicationContext​​方法。

如下方法会在​​AbstractHandlerMapping​​​的子类如​​SimpleUrlHandlerMapping​​​、​​BeanNameUrlHandlerMapping​​​、​​RequestMappingHandlerMapping​​等实例化时被调用。

@Override
protected void initApplicationContext() throws BeansException {
extendInterceptors(this.interceptors);
detectMappedInterceptors(this.adaptedInterceptors);
initInterceptors();
}

① 核心方法getHandler

再看下核心方法​​public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {}​

@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);
}
// 根据handler request获取HandlerExecutionChain
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);

if (logger.isTraceEnabled()) {
logger.trace("Mapped to " + handler);
}
else if (logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) {
logger.debug("Mapped to " + executionChain.getHandler());
}

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;
}

方法解释如下:

  • ① 获取handler ;
  • ② 如果① 获取的handler为null,则获取一个默认的handler也就是defaultHandler;
  • ③ 如果②仍旧没有获取到handler,则返回null;
  • ④ 根据handler获取HandlerExecutionChain,其包括了handler和拦截器;
  • ⑤ 如果进行了跨域配置,则获取CorsHandlerExecutionChain

② 核心方法getHandlerExecutionChain

我们看内部的核心方法​​getHandlerExecutionChain​

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;
}

方法解释如下:

  • ① 处理获取一个​​HandlerExecutionChain​​ ;
  • ② 遍历​​adaptedInterceptors​
  • ③ 如果interceptor 是 MappedInterceptor则判断是否匹配当前请求,匹配则添加到当前处理器链中
  • ④ 如果interceptor 是普通拦截器(没有指定映射URL则默认拦截所有),则直接添加到当前处理器链中

【2】 AbstractUrlHandlerMapping

该分支下是根据URL进行处理器映射匹配查找。常见实现子类有​​SimpleUrlHandlerMapping​​​和​​BeanNameUrlHandlerMapping​​​。其覆盖了父类​​AbstractHandlerMapping​​​的​​getHandlerInternal​​方法。

如下所示,其内部维护了两个final ​​map:handlerMap​​​ 维护了一个​​URL与handler​​​的映射关系;​​pathPatternHandlerMap​​​ 维护了一个​​PathPattern与handler​​的映射关系。当获取​​handler​​时就从这两个map中进行遍历查找。

// URL模式--handler
private final Map<String, Object> handlerMap = new LinkedHashMap<>();

// PathPattern模式--handler
private final Map<PathPattern, Object> pathPatternHandlerMap = new LinkedHashMap<>();

① 核心方法getHandlerInternal

​getHandlerInternal​​方法会根据request获取一个handler。

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;
}

方法解释如下:

  • ① 根据request获取lookupPath;
  • ② 如果patternParser不为null,调用​​lookupHandler(path, lookupPath, request)​​​也就是从​​pathPatternHandlerMap​​寻找handler;
  • ③ 如果patternParser为null,则调用​​lookupHandler(lookupPath, request)​​也就是从handlerMap进行查找;
  • ④ 如果②③之后handler还为null,则尝试获取RootHandler、DefaultHandler。
  • ⑤ 如果找到的 Handler 是一个 String,则去 Spring 容器中查找该 String 对应的 Bean;
  • ⑥ 调用 validateHandler 方法来校验找到的 handler 和 request 是否匹配,这是一个空方法,可以忽略。
  • ⑦ 通过​​buildPathExposingHandler​​​ 方法将找到的handler包装为​​HandlerExecutionChain​​​然后添加​​PathExposingHandlerInterceptor​​​拦截器,并根据​​uriTemplateVariables​​​是否为空添加​​UriTemplateVariablesHandlerInterceptor​​拦截器。

② 核心方法​​lookupHandler(String lookupPath, HttpServletRequest request)​

也就是根据URL获取一个handler。

@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;
// AntPathMatcher.AntPatternComparator
Comparator<String> patternComparator = getPathMatcher().getPatternComparator(lookupPath);
if (!matchingPatterns.isEmpty()) {
matchingPatterns.sort(patternComparator);
if (logger.isTraceEnabled() && matchingPatterns.size() > 1) {
logger.trace("Matching patterns " + matchingPatterns);
}
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);
}
}
if (logger.isTraceEnabled() && uriTemplateVariables.size() > 0) {
logger.trace("URI variables " + uriTemplateVariables);
}
return buildPathExposingHandler(handler, bestMatch, pathWithinMapping, uriTemplateVariables);
}

// No handler found...
return null;
}

方法解释如下:

  • ① 根据lookupPath从handlerMap中获取对应的handler,如果存在则直接返回。注意,这里是直接匹配,也就是​​没有通配符​​。
  • ② 遍历handlerMap:
  • ③ 如果当前key与lookupPath匹配(由​​AntPathMatcher​​​决定),则添加当前key到​​List<String> matchingPatterns​​中;
  • ④ 如果当前​​key+"/"​​​ 与lookupPath匹配(由​​AntPathMatcher​​​决定),则添加当前​​key+"/"​​​到​​List<String> matchingPatterns​​中;
  • ⑤ 根据​​PathMatcher​​​获取​​PatternComparator​​​,默认是​​AntPatternComparator​​;
  • ⑥ 如果​​matchingPatterns​​​不为空,则根据​​PatternComparator​​进行排序然后获取第一个元素赋予bestMatch;
  • ⑦ 如果获取的bestMatch不为空则从handlerMap中获取对应的handler;
  • ⑧ 如果⑦获取的handler为空且​​bestMatch.endsWith("/")​​​,则​​bestMatch​​​去掉​​"/"​​​再次从​​handlerMap​​获取handler;
  • ⑨ 如果⑧获取的handler仍旧为null,则抛出异常。
  • (10) 如果找到的 handler 是 String 类型的,则再去 Spring 容器中查找对应的 Bean,接下来再调用 validateHandler 方法进行验证;
  • (11) extractPathWithinPattern 方法提取出匹配部分​​pattern-matched​​​路径。例如定义的接口规则是​​myroot/*.html​​​,请求路径是​​myroot/myfile.html​​​,那么最终获取到的就是​​myfile.html​​。
  • (12) 遍历matchingPatterns,然后​​patternComparator.compare(bestMatch, matchingPattern) == 0​​​ 也就是判断当前​​matchingPattern​​​是否与bestMatch​​等价​​​。然后根据每一个​​matchingPattern​​​与​​lookupPath​​​尝试解析path里面变量放入​​Map<String, String> uriTemplateVariables​​;
  • (13) 调用buildPathExposingHandler,这里不再赘述。
  • (14) 如果获取的bestMatch为空,则直接返回null。

由于一个请求 可能会和定义的多个接口匹配上(接口定义的URL带有通配符),所以 matchingPatterns 变量是一个数组。接下来就要对 matchingPatterns 进行排序,排序完成后,选择排序后的第一项作为最佳选项赋值给 bestMatch 变量。默认的排序规则是 ​​AntPatternComparator​​​,当然开发者也可以自定义。​​AntPatternComparator​​ 中定义的优先级如下:

URL

优先级

不含任何特殊符号的路径,如:​​/a/b/c​

第一优先级

带有​​{}​​​的路径,如:​​/a/{b}/c​

第二优先级

带有正则的路径,如:​​/a/{regex:\d{3}}/c​

第三优先级

带有​​*​​​的路径,如:​​/a/b/*​

第四优先级

带有​​**​​​的路径,如:​​/a/b/**​

第五优先级

最模糊的匹配:​​/**​

最低优先级

③ 核心方法registerHandler

该方法就是扫描并注册handler,在bean实例化时被调用,如下所示。其中detectHandlers会调用​​registerHandler(urls, beanName)​​进行遍历循环注册。

@Override
public void initApplicationContext() throws ApplicationContextException {
super.initApplicationContext();
detectHandlers();
}

这里我们直接看​​registerHandler(urlPath, beanName)​​​方法,其尝试将​​urlPath = resolvedHandler​​放入handlerMap中。

protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
Assert.notNull(urlPath, "URL path must not be null");
Assert.notNull(handler, "Handler object must not be null");
Object resolvedHandler = handler;

// Eagerly resolve handler if referencing singleton via name.
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("/")) {
if (logger.isTraceEnabled()) {
logger.trace("Root mapping to " + getHandlerDescription(handler));
}
setRootHandler(resolvedHandler);
}
else if (urlPath.equals("/*")) {
if (logger.isTraceEnabled()) {
logger.trace("Default mapping to " + getHandlerDescription(handler));
}
setDefaultHandler(resolvedHandler);
}
else {
this.handlerMap.put(urlPath, resolvedHandler);
if (getPatternParser() != null) {
this.pathPatternHandlerMap.put(getPatternParser().parse(urlPath), resolvedHandler);
}
if (logger.isTraceEnabled()) {
logger.trace("Mapped [" + urlPath + "] onto " + getHandlerDescription(handler));
}
}
}
}

方法源码解释如下:

  • ①​​Object resolvedHandler = handler​​​,如果​​!this.lazyInitHandlers && handler instanceof String​​,则尝试从Spring容器中获取resolvedHandler;
  • ② 根据​​urlPath​​​尝试从​​handlerMap​​​获取​​handler​​,如果handler不为空且不等于入参中的handler则抛出异常。如果不为空且等于入参中的handler,则表示已经存在不做处理。
  • ③ 如果从handlerMap获取不到handler:
  • ④ 如果​​urlPath.equals("/")​​​,则将​​resolvedHandler​​​设置为​​RootHandler​​;
  • ⑤​​urlPath.equals("/*")​​​,则将​​resolvedHandler​​​设置为​​DefaultHandler​​;
  • ⑥​​this.handlerMap.put(urlPath, resolvedHandler);​​将其放入handlerMap。

【3】SimpleUrlHandlerMapping

顾名思义,就是在​​URL与实例bean(或bean 名称)​​​维护映射关系。其内部维护了一个final map​​private final Map<String, Object> urlMap = new LinkedHashMap<>();​​。

其构造方法如下所示:

private final Map<String, Object> urlMap = new LinkedHashMap<>();

// 空的构造函数
public SimpleUrlHandlerMapping() {
}
//根据urlMap 初始化 final Map<String, Object> urlMap
public SimpleUrlHandlerMapping(Map<String, ?> urlMap) {
setUrlMap(urlMap);
}
// 在前者的基础上并设置当前handleMapping的order
public SimpleUrlHandlerMapping(Map<String, ?> urlMap, int order) {
setUrlMap(urlMap);
setOrder(order);
}

其重写了父类的​​initApplicationContext​​​方法,在该方法中调用了父类​​initApplicationContext​​​方法,并调用了​​registerHandlers(this.urlMap);​​。

@Override
public void initApplicationContext() throws BeansException {
super.initApplicationContext();
registerHandlers(this.urlMap);
}

① 核心方法registerHandlers

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);
});
if (logger.isDebugEnabled()) {
List<String> patterns = new ArrayList<>();
if (getRootHandler() != null) {
patterns.add("/");
}
if (getDefaultHandler() != null) {
patterns.add("/**");
}
patterns.addAll(getHandlerMap().keySet());
logger.debug("Patterns " + patterns + " in " + formatMappingName());
}
}
}

方法解释如下:

  • ① 如果urlMap为空,则直接返回;
  • ② 遍历循环urlMap,如果当前URL不是以​​"/"​​​开头,则拼接前缀​​"/"​​​;如果handler是string,则进行trim;然后调用父类​​AbstractUrlHandlerMapping的registerHandler​​方法。

② SimpleUrlHandlerMapping的使用

一个应用实例是如下配置,则这里urlMap就维护了映射关系。

<mvc:view-controller path="/" view-name="index"></mvc:view-controller>

SpringMVC常见组件之HandlerMapping分析_spring_02
也就是说,如果你想使用SimpleUrlHandlerMapping,那么请自行配置映射关系。如下xml配置:

<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="urlMap">
<map>
<entry key="/hello" value="helloController"/>
</map>
</property>
<!--或者如下配置-->
<property name="mappings">
<props>
<prop key="/hello">helloController</prop>
</props>
</property>
<!--或者如下配置-->
<property name="mappings">
<value>
/hello=helloController
</value>
</property>
</bean>

helloController是controller的名字,需要实现Controller接口,如下所示:

@org.springframework.stereotype.Controller
public class HelloController implements Controller {

@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
System.out.println(request.getRequestURI());
System.out.println(this.getClass().getName());
ModelAndView modelAndView=new ModelAndView();
modelAndView.setViewName("success");
return modelAndView;
}
}

如果启用了​​defaultServletHandler​​​那么​​SimpleUrlHandlerMapping​​​的​​urlMap​​中还有如下配置:

/**=org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler#0

SpringMVC常见组件之HandlerMapping分析_请求映射处理_03

【4】AbstractDetectingUrlHandlerMapping

这个简单来讲,就是扫描spring容器中注册的bean。如果bean name或bean的别名是以​​"/"​​​开头,那么就将bean name或bean的别名作为URL,调用父类​​AbstractUrlHandlerMapping的registerHandler(String[] urlPaths, String beanName)​​方法最后注册到handlerMap中。

我们同样从​​initApplicationContext​​​方法看起,其调用了父类的​​AbstractHandlerMapping​​​的​​initApplicationContext​​方法并调用detectHandlers来检测handler。

@Override
public void initApplicationContext() throws ApplicationContextException {
super.initApplicationContext();
detectHandlers();
}

① 核心方法detectHandlers

protected void detectHandlers() throws BeansException {
ApplicationContext applicationContext = obtainApplicationContext();
String[] beanNames = (this.detectHandlersInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, Object.class) :
applicationContext.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);
}
}
// ... 这里是无需关注的日志打印
}

方法解释如下:

  • ① 获取ApplicationContext;
  • ② 获取容器中的beanNames ,这里得到一个数组;
  • ③ 遍历beanNames ,检测其URL。如果URL不为空则注册到handlerMap。

SpringMVC常见组件之HandlerMapping分析_子类_04

这里​​determineUrlsForHandler​​​是个抽象方法,供子类实现。其只有一个子类​​BeanNameUrlHandlerMapping​​,我们看下其实现determineUrlsForHandler方法。

@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);
}

方法很简单,判断​​beanName是否以"/"​​​开头,是的话放入urls数组中。然后获取​​beanName​​的别名,对别名数组进行遍历判断是否以否以"/"开头,是的话放入urls数组中。

也就是说,上面我们的HelloController可以指定名字如下所示:

@org.springframework.stereotype.Controller("/helloController")
public class HelloController implements Controller {

@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
System.out.println(request.getRequestURI());
System.out.println(this.getClass().getName());
ModelAndView modelAndView=new ModelAndView();
modelAndView.setViewName("success");
return modelAndView;
}
}

【5】AbstractHandlerMethodMapping

在前面 AbstractUrlHandlerMapping 体系下,一个 Handler 一般就是一个类(​​显示Controller如HelloController或者隐式controller如ParameterizableViewController​​​)。但是在 ​​AbstractHandlerMethodMapping​​​ 体系下,一个 ​​Handler​​​ 通常指的是一个 ​​Mehtod​​​,是​​HandlerMethod​​类型。

​AbstractHandlerMethodMapping​​主要维护了请求和​​handlerMethod​​​之间的映射关系。其覆盖了父类​​AbstractHandlerMapping​​如下三个方法:

// 核心方法,获取内部handler。该体系下返回handlerMethod
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {}

//如下两个方法和跨域配置有关
protected CorsConfiguration getCorsConfiguration(Object handler, HttpServletRequest request) {}
protected boolean hasCorsConfigurationSource(Object handler) {}

其提供了如下抽象方法供子类实现

//RequestMappingInfoHandlerMapping实现
protected abstract T getMatchingMapping(T mapping, HttpServletRequest request);

//RequestMappingInfoHandlerMapping实现
protected abstract Comparator<T> getMappingComparator(HttpServletRequest request);

//RequestMappingHandlerMapping实现
protected abstract boolean isHandler(Class<?> beanType);

//RequestMappingHandlerMapping实现
protected abstract T getMappingForMethod(Method method, Class<?> handlerType);

核心变量​​private final MappingRegistry mappingRegistry = new MappingRegistry();​ 为什么说是核心变量?我们可以看下mappingRegistry究竟是个什么?如下所示其registry 维护了所有的​​RequestMappingInfo实例与MappingRegistration​​​映射关系。pathLookup 维护了​​directPath与List<RequestMappingInfo>​​实例关系。当处理请求获取handlerMethod时,qi

private final Map<T, MappingRegistration<T>> registry = new HashMap<>();

private final MultiValueMap<String, T> pathLookup = new LinkedMultiValueMap<>();

private final Map<String, List<HandlerMethod>> nameLookup = new ConcurrentHashMap<>();

private final Map<HandlerMethod, CorsConfiguration> corsLookup = new ConcurrentHashMap<>();

private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();

我们看一下该体系下的家族树图示

SpringMVC常见组件之HandlerMapping分析_请求映射处理_05

需要注意的是​​AbstractHandlerMethodMapping​​​其实现了InitializingBean接口,重写了afterPropertiesSet方法。为什么这里特意提一下实现了​​InitializingBean​​​接口,那是因为这个接口定义了其实现Bean在容器完成属性设置后可以执行自定义初始化操作,我们的​​AbstractHandlerMethodMapping​​​便实现了这个接口,并且定义了一组自定义操作,就是用来检测处理我们的​​@RequestMapping​​注解。

① afterPropertiesSet

如下图所示,如何bean实现了​​InitializingBean​​​接口,那么在初始化过程中一定会调用其​​afterPropertiesSet​​​方法。
SpringMVC常见组件之HandlerMapping分析_ide_06
这里我们看一下​​​RequestMappingHandlerMapping.afterPropertiesSet​​方法。

@Override
@SuppressWarnings("deprecation")
public void afterPropertiesSet() {

this.config = new RequestMappingInfo.BuilderConfiguration();
this.config.setTrailingSlashMatch(useTrailingSlashMatch());
this.config.setContentNegotiationManager(getContentNegotiationManager());

if (getPatternParser() != null) {
this.config.setPatternParser(getPatternParser());
Assert.isTrue(!this.useSuffixPatternMatch && !this.useRegisteredSuffixPatternMatch,
"Suffix pattern matching not supported with PathPatternParser.");
}
else {
this.config.setSuffixPatternMatch(useSuffixPatternMatch());
this.config.setRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch());
this.config.setPathMatcher(getPathMatcher());
}

super.afterPropertiesSet();
}

可以看到其主要做了一些配置如后缀模式匹配,然后调用父类​​AbstractHandlerMethodMapping​​​的​​afterPropertiesSet​​方法。其源码如下所示-在初始化过程中检测处理器方法。

@Override
public void afterPropertiesSet() {
initHandlerMethods();
}

简单直接调用了​​AbstractHandlerMethodMapping#initHandlerMethods​​方法。如下所示父类直接调用initHandlerMethods方法进行handlerMethod的初始化注册。

@Override
public void afterPropertiesSet() {
initHandlerMethods();
}

① initHandlerMethods

​initHandlerMethods​​方法源码如下:

// 在ApplicationContext中扫描bean,检测并注册 handler methods
protected void initHandlerMethods() {
for (String beanName : getCandidateBeanNames()) {
// 如果beanName不是以scopedTarget开头,就执行processCandidateBean
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
processCandidateBean(beanName);
}
}
handlerMethodsInitialized(getHandlerMethods());
}

这里我们着重分析​​processCandidateBean(beanName);​​​和​​getHandlerMethods()​​方法。

② processCandidateBean

​processCandidateBean(beanName);​​​方法的作用是什么呢?判断其是否为一个​​handler​​​(也就是是否拥有​​@Controller​​​注解或​​@RequestMapping​​注解)。如果是一个handler,则检测并注册其内部方法。

protected void processCandidateBean(String beanName) {
Class<?> beanType = null;
try {
beanType = obtainApplicationContext().getType(beanName);
}
catch (Throwable ex) {
// An unresolvable bean type, probably from a lazy bean - let's ignore it.
if (logger.isTraceEnabled()) {
logger.trace("Could not resolve type for bean '" + beanName + "'", ex);
}
}
if (beanType != null && isHandler(beanType)) {
// 注意,这里是核心
detectHandlerMethods(beanName);
}
}

​isHandler(beanType)​​​是一个抽象方法,在子类​​RequestMappingHandlerMapping​​中实现。

我们看下​​isHandler(beanType)​​​是如何判断当前bean是否为一个​​handler​​的?

@Override
protected boolean isHandler(Class<?> beanType) {
return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}

判断类上面的注解是否是(或拥有)​​@Controller​​​或者​​@RequestMapping​​​(或者复合注解如​​@RestController、@PostMapping​​)。OK,我们继续看detectHandlerMethods方法如何扫描并注册。

③ detectHandlerMethods

扫描handler内部的handler method然后注册到​​AbstractHandlerMethodMapping​​​内部的常量​​private final MappingRegistry mappingRegistry = new MappingRegistry();​​中。

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);
}
});
if (logger.isTraceEnabled()) {
logger.trace(formatMappings(userType, methods));
}
methods.forEach((method, mapping) -> {
Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
registerHandlerMethod(handler, invocableMethod, mapping);
});
}
}

这里我们暂不展开分析,放在第三部分中展开。我们只需要知道在​​RequestMappingHandlerMapping​​ 初始化过程中,该方法将会扫描容器中那些Controller及内部method。获取handler、method、mapping,也就是处理器、处理器方法与请求映射信息(包括@RequestMapping标注的那些参数)。

扫描并注册完之后,到了​​handlerMethodsInitialized(getHandlerMethods());​​​。这里我们看下​​getHandlerMethods​​。

④ getHandlerMethods

​AbstractHandlerMethodMapping#getHandlerMethods​​方法如下:

// 返回只读的map  mapping :HandlerMethod
public Map<T, HandlerMethod> getHandlerMethods() {
// private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
this.mappingRegistry.acquireReadLock();
try {
return Collections.unmodifiableMap(
this.mappingRegistry.getRegistrations().entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().handlerMethod)));
}
finally {
this.mappingRegistry.releaseReadLock();
}
}

方法源码解释如下:

  • 获取mappingRegistry中的读锁然后加锁;
  • 遍历MappingRegistry.registry,返回一个只读Map;
  • 释放锁

​handlerMethodsInitialized(getHandlerMethods());​​方法则更简单,只是简单打印告诉系统已经已经有多少个mappings被扫描注册。

【6】detectHandlerMethods详细分析

​detectHandlerMethods​​​方法是该组件的核心方法,将会扫描​​@RequestMapping​​​注解的方法并创建​​RequestMappingInfo​​​实例。然后将将​​RequestMappingInfo​​​实例以及处理器方法注册到​​AbstractHandlerMethodMapping​​​的内部类​​MappingRegistry​​​中常量Map​​private final Map<T, MappingRegistration<T>> registry = new HashMap<>();​​。

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);
}
});
if (logger.isTraceEnabled()) {
logger.trace(formatMappings(userType, methods));
}
methods.forEach((method, mapping) -> {
Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
registerHandlerMethod(handler, invocableMethod, mapping);
});
}
}

代码解释如下:

  • ​getMappingForMethod​​​方法创建​​RequestMappingInfo​​实例;
  • ​methods.forEach​​​方法循环遍历并注册​​handler​​​、​​invocableMethod​​​、​​mapping​​到内存中。

这里我们可以先看下method、mapping的实例
SpringMVC常见组件之HandlerMapping分析_ide_07
如上图所示,method记录了方法详细信息诸如声明类、名字、返回结果类型、参数类型、异常类型、修饰符、注解、参数、是否覆盖等等。mapping则记录了@RequestMapping注解里面的信息如URL、请求方法、参数限制、header、consume、produce以及自定义限制条件等。

① getMappingForMethod

@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​​​创建​​RequestMappingInfo​​​ 实例​​info​​ ;
  • ② 根据​​handlerType​​​创建​​RequestMappingInfo​​​ 实例实例​​typeInfo​​ ;
  • ③ 如果②中创建的实例​​typeInfo​​​ 不为​​null​​​,则​​info = typeInfo.combine(info)​​进行合并;
  • ④ 根据​​handlerType​​​获取类上面定义的路径前缀,如果前缀不为空则对​​info​​进行合并。

② 创建RequestMappingInfo实例

​RequestMappingHandlerMapping#createRequestMappingInfo​​源码如下所示:

@Nullable
private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
// //获取方法method上的@RequestMapping实例。
RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
// 这里始终为null
RequestCondition<?> condition = (element instanceof Class ?
getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));
return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
}

然后我们继续看​​createRequestMappingInfo(requestMapping, condition)​​方法。

protected RequestMappingInfo createRequestMappingInfo(
RequestMapping requestMapping, @Nullable RequestCondition<?> customCondition) {

RequestMappingInfo.Builder builder = RequestMappingInfo
.paths(resolveEmbeddedValuesInPatterns(requestMapping.path()))
.methods(requestMapping.method())
.params(requestMapping.params())
.headers(requestMapping.headers())
.consumes(requestMapping.consumes())
.produces(requestMapping.produces())
.mappingName(requestMapping.name());
if (customCondition != null) {
builder.customCondition(customCondition);
}
return builder.options(this.config).build();
}

看着是不是会熟悉?其实就是获取​​@RequestMapping​​注解的url、params、headers、consumes、produces以及name最后进行创建RequestMappingInfo实例。

关于​​@RequestMapping​​​注解的url、params、headers、consumes、produces以及name这些本文不做赘述,更多信息参考博文SpringMVC-@RequestMapping的参数和用法

@RequestMapping注解信息实例

@org.springframework.web.bind.annotation.RequestMapping(consumes=[], headers=[], method=[], name=, params=[], path=[/**/testInterceptor], produces=[], value=[/**/testInterceptor])

RequestMappingInfo实例
SpringMVC常见组件之HandlerMapping分析_ide_08

③ 类与方法上面的请求映射实例合并typeInfo.combine(info)

这里我们着重看一下根据类创建的​​RequestMappingInfo​​​实例与根据方法创建的​​RequestMappingInfo​​实例合并过程。

@Override
public RequestMappingInfo combine(RequestMappingInfo other) {
String name = combineNames(other);

PathPatternsRequestCondition pathPatterns =
(this.pathPatternsCondition != null && other.pathPatternsCondition != null ?
this.pathPatternsCondition.combine(other.pathPatternsCondition) : null);

PatternsRequestCondition patterns =
(this.patternsCondition != null && other.patternsCondition != null ?
this.patternsCondition.combine(other.patternsCondition) : null);

RequestMethodsRequestCondition methods = this.methodsCondition.combine(other.methodsCondition);
ParamsRequestCondition params = this.paramsCondition.combine(other.paramsCondition);
HeadersRequestCondition headers = this.headersCondition.combine(other.headersCondition);
ConsumesRequestCondition consumes = this.consumesCondition.combine(other.consumesCondition);
ProducesRequestCondition produces = this.producesCondition.combine(other.producesCondition);
RequestConditionHolder custom = this.customConditionHolder.combine(other.customConditionHolder);

return new RequestMappingInfo(
name, pathPatterns, patterns, methods, params, headers, consumes, produces, custom);
}

这里涉及到了几个类,我们大致了解下含义:

  • ​PatternRequestCondition​​​ 它其实就是URL模式的封装,它包含了一个URL模式的Set集合。其实就是​​@RequestMapping​​注解中的value值得封装。
  • ​RequestMethodRequestCondition​​ 它是@RequestMapping 注解中method属性的封装
  • ​ParamsRequestCondition​​ 它是@RequestMapping注解中params属性的封装

等等,依次类推。因此​​RequestMappingInfo​​​其实就是对​​@RquestMapping​​​ 的封装。这里关于method、params、heade等的合并我们暂时不理会(其实你对框架应用熟悉就应该能想到这些合并就是做​​条件与​​​),着重看下​​patternsCondition​​的合并。

@Override
public PatternsRequestCondition combine(PatternsRequestCondition other) {
if (isEmptyPathMapping() && other.isEmptyPathMapping()) {
return this;
}
else if (other.isEmptyPathMapping()) {
return this;
}
else if (isEmptyPathMapping()) {
return other;
}
Set<String> result = new LinkedHashSet<>();
if (!this.patterns.isEmpty() && !other.patterns.isEmpty()) {
for (String pattern1 : this.patterns) {
for (String pattern2 : other.patterns) {
result.add(this.pathMatcher.combine(pattern1, pattern2));
}
}
}
return new PatternsRequestCondition(result, this);
}

代码解释如下:

  • 如果两个path都为空,则返回this;
  • 如果其中一个为空,返回不为空的那个;
  • 如果两个都不为空,则进行笛卡尔积合并;

两个pattern如何合并的呢?url拼接是由PathMatcher来完成的,我们看下实例

pattern1

pattern2

结果

​/*​

/hotel

/hotel

​/*.*​

​/*.html​

​/*.html​

/usr

/user

/usr/user

/{foo}

/bar

/{foo}/bar

​/hotels/*​

/booking

/hotels/booking

​/hotels/*​

booking

/hotels/booking

/hotels

/booking

/hotels/booking

/hotels

booking

/hotels/booking

​/*.html​

/hotel

/hotel.html

​/*.html​

​/hotel.*​

/hotel.html

④ 注册HandlerMethod

从上面看到这里会进行循环然后进行注册,如下所示:

methods.forEach((method, mapping) -> {
Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
registerHandlerMethod(handler, invocableMethod, mapping);
});

​AbstractHandlerMethodMapping#registerHandlerMethod​​方法如下:

protected void registerHandlerMethod(Object handler, Method method, T mapping) {
this.mappingRegistry.register(mapping, handler, method);
}

继续跟进​​AbstractHandlerMethodMapping.MappingRegistry#register​​:

public void register(T mapping, Object handler, Method method) {
this.readWriteLock.writeLock().lock();
try {
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
// 这里mapping就是RequestMappingInfo实例
validateMethodMapping(handlerMethod, mapping);
// 获取请求URL ,然后往pathLookup放入path与mapping
Set<String> directPaths = AbstractHandlerMethodMapping.this.getDirectPaths(mapping);
for (String path : directPaths) {
this.pathLookup.add(path, mapping);
}
// 获取name,然后往nameLookup中放入name=List<HandlerMethod>
String name = null;
if (getNamingStrategy() != null) {
name = getNamingStrategy().getName(handlerMethod, mapping);
addMappingName(name, handlerMethod);
}

CorsConfiguration config = initCorsConfiguration(handler, method, mapping);
if (config != null) {
config.validateAllowCredentials();
this.corsLookup.put(handlerMethod, config);
}

this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directPaths, name));
}
finally {
this.readWriteLock.writeLock().unlock();
}
}

代码解释如下:

  • ① 获取写锁进行加锁;
  • ② 获取HandlerMethod;
    SpringMVC常见组件之HandlerMapping分析_spring_09
  • ③ 校验是否存在不同的​​HandlerMethod​​对应同一个​​RequestMappingInfo​​实例。简单来说就是​​同样的@RequestMapping注解​​在不同的类或者方法上。
  • ④ 获取映射URL,然后放到​​AbstractHandlerMethodMapping​​内部类​​MappingRegistry​​的​​private final MultiValueMap<String, T> pathLookup​​中,​​path=RequestMappingInfo​​。这里放的URL是​​不带通配符*和?​​的,也就是所谓的​​directPath​​。
    SpringMVC常见组件之HandlerMapping分析_spring_10
  • ⑤ 获取handler method的name,然后往​​nameLookup​​中放入​​name=List<HandlerMethod>​​。这里类似于一个彩蛋,如URL​​/test​​访问也可以使用方法​​TC#test​​访问。TC就是​​TestController​​的缩写。
    SpringMVC常见组件之HandlerMapping分析_子类_11
  • ⑥ 判断是否设置了跨域也就是类或者方法上是否有@CrossOrigin注解。如果设置了跨域则初始化CorsConfiguration:
  • 校验是否满足跨域条件:​​allowCredentials为true时allowedOrigins不能为*​​,不满足则会抛出异常;
  • 如果满足条件则往​​corsLookup​​​放入​​handlerMethod=config​​​:​​this.corsLookup.put(handlerMethod, config);​
  • ⑦​​this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directPaths, name));​​往​​AbstractHandlerMethodMapping​​的内部类​​MappingRegistry​​的​​private final Map<T, MappingRegistration<T>> registry = new HashMap<>();​​放入​​mapping=new MappingRegistration<>(mapping, handlerMethod, directPaths, name)​SpringMVC常见组件之HandlerMapping分析_请求映射处理_12
  • ⑧ 获取写锁,进行锁的释放。

【7】 查找lookupHandlerMethod

​AbstractHandlerMethodMapping#lookupHandlerMethod​​方法主要是根据lookupPath来获取一个HandlerMethod 。

① lookupHandlerMethod

@Nullable
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
List<Match> matches = new ArrayList<>();
// 从 MappingRegistry.pathLookup中获取 RequestMappingInfo实例集合
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 (logger.isTraceEnabled()) {
logger.trace(matches.size() + " matching mappings: " + matches);
}
if (CorsUtils.isPreFlightRequest(request)) {
return PREFLIGHT_AMBIGUOUS_MATCH;
}
Match secondBestMatch = matches.get(1);
if (comparator.compare(bestMatch, secondBestMatch) == 0) {
Method m1 = bestMatch.handlerMethod.getMethod();
Method m2 = secondBestMatch.handlerMethod.getMethod();
String uri = request.getRequestURI();
throw new IllegalStateException(
"Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
}
}
request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
handleMatch(bestMatch.mapping, lookupPath, request);
return bestMatch.handlerMethod;
}
else {
return handleNoMatch(this.mappingRegistry.getRegistrations().keySet(), lookupPath, request);
}
}

mappingRegistry实例图示

SpringMVC常见组件之HandlerMapping分析_子类_13

方法解释如下:

  • ① 根据lookupPath从​​MappingRegistry的MultiValueMap<String, T> pathLookup​​​获取​​List<T> directPathMatches​​​。如果获取到结果不为空,则找出匹配当前请求的​​RequestMappingInfo​​​实例,根据​​RequestMappingInfo​​​实例实例和​​HandlerMethod​​​组装Match,最后放到​​List<Match> matches​​​中。这里我们可以再回顾一下pathLookup实例:
    SpringMVC常见组件之HandlerMapping分析_ide_14
  • ② 如果①操作后的matches为空,则尝试从​​MappingRegistry.registry​​中获取所有匹配当前请求的数据集,然后封装为Match实例对象放到matches中。
  • ③ 如果② 之后matches不为空,则判断是否只有一条。如果数据集大于一条进行排序,​​bestMatch = matches.get(0);​​​。这里会额外对第一、二条数据进行比较,如果相等则抛出异常。如果数据集只有一条,则​​bestMatch = matches.get(0);​​,继续往下处理。
  • ④ 往request中放入两个属性:
  • ​request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);​
  • .​​request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, lookupPath);​
  • ⑤ 返回​​bestMatch​​​中的​​handlerMethod​​.
  • ⑥ 如果② 之后matches为空,则走​​handleNoMatch​​​方法。该方法为空,返回null,在子类​​RequestMappingInfoHandlerMapping​​​中重写了该方法可能会抛出诸如​​HttpRequestMethodNotSupportedException、HttpMediaTypeNotSupportedException、HttpMediaTypeNotAcceptableException、UnsatisfiedServletRequestParameterException​​异常。

request中放的两个属性键是:

// org.springframework.web.servlet.HandlerMapping.bestMatchingHandler
String BEST_MATCHING_HANDLER_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingHandler";

// org.springframework.web.servlet.HandlerMapping.pathWithinHandlerMapping
String PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE = HandlerMapping.class.getName() + ".pathWithinHandlerMapping";

上面我们看到了如果matches数据集大于一条会获取比较器然后进行排序,其会调用​​RequestMappingInfo#compareTo​​方法进行比较。

Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
matches.sort(comparator);

② addMatchingMappings

这里我们再看下addMatchingMappings方法。方法如下所示,会遍历mappings集合(RequestMappingInfo实例集合),从中找到匹配当前request的​​RequestMappingInfo​​​,然后封装为Match对象放到​​List<Match> matches​​中。

private void addMatchingMappings(Collection<T> mappings, List<Match> matches, HttpServletRequest request) {
for (T mapping : mappings) {
T match = getMatchingMapping(mapping, request);
if (match != null) {
matches.add(new Match(match,
this.mappingRegistry.getRegistrations().get(mapping).getHandlerMethod()));
}
}
}

③ getMatchingMapping&&getMatchingCondition

那么如何判断RequestMappingInfo与request是否匹配呢? 我们继续看下​​getMatchingMapping​​​方法。该方法在​​RequestMappingInfoHandlerMapping​​子类中进行了实现。

protected RequestMappingInfo getMatchingMapping(RequestMappingInfo info, HttpServletRequest request) {
return info.getMatchingCondition(request);
}

//RequestMappingInfo#getMatchingCondition
public RequestMappingInfo getMatchingCondition(HttpServletRequest request) {
RequestMethodsRequestCondition methods = this.methodsCondition.getMatchingCondition(request);
if (methods == null) {
return null;
}
ParamsRequestCondition params = this.paramsCondition.getMatchingCondition(request);
if (params == null) {
return null;
}
HeadersRequestCondition headers = this.headersCondition.getMatchingCondition(request);
if (headers == null) {
return null;
}
ConsumesRequestCondition consumes = this.consumesCondition.getMatchingCondition(request);
if (consumes == null) {
return null;
}
ProducesRequestCondition produces = this.producesCondition.getMatchingCondition(request);
if (produces == null) {
return null;
}
PathPatternsRequestCondition pathPatterns = null;
if (this.pathPatternsCondition != null) {
pathPatterns = this.pathPatternsCondition.getMatchingCondition(request);
if (pathPatterns == null) {
return null;
}
}
PatternsRequestCondition patterns = null;
if (this.patternsCondition != null) {
patterns = this.patternsCondition.getMatchingCondition(request);
if (patterns == null) {
return null;
}
}
RequestConditionHolder custom = this.customConditionHolder.getMatchingCondition(request);
if (custom == null) {
return null;
}
return new RequestMappingInfo(
this.name, pathPatterns, patterns, methods, params, headers, consumes, produces, custom);
}

如上所示,方法会逐个对method、param、header、consume、produce、pathPattern、pattern以及自定义condition进行匹配,只有全部满足则通过新建一个RequestMappingInfo实例返回,否则返回null。

总结

  • ① 在bean实例化时扫描、注册请求URL(或者是RequestMappingInfo实例)与handler;
  • ② 请求处理时,遍历查找。如果不能直接获取或者获取为多个,则会进行比较排序然后选出最优解;
  • ③ 封装为HandlerExecutionChain返回。

【8】几个概念

① RequestMappingInfo

这个类是对请求映射的一个抽象,它包含了请求路径,请求方法,请求头等信息。其实可以看做是​​@RequestMapping​​​的一个对应类。其内部维护了静态内部类​​BuilderConfiguration​​​、​​DefaultBuilder​​​以及一些封装​​@RequestMapping​​注解信息的类。

其内部常量如下所示:

// 一个逻辑分离 (' || ') 请求条件,它根据一组 URL 路径模式匹配请求。这里使用已解析的PathPatterns
private static final PathPatternsRequestCondition EMPTY_PATH_PATTERNS = new PathPatternsRequestCondition();
// Patterns 也就是请求URL,PathPatternsRequestCondition 不同,这里使用String 字符串模式
private static final PatternsRequestCondition EMPTY_PATTERNS = new PatternsRequestCondition();
// 请求限制http method
private static final RequestMethodsRequestCondition EMPTY_REQUEST_METHODS = new RequestMethodsRequestCondition();
// @RequestMapping注解中的param
private static final ParamsRequestCondition EMPTY_PARAMS = new ParamsRequestCondition();
// @RequestMapping注解中的header
private static final HeadersRequestCondition EMPTY_HEADERS = new HeadersRequestCondition();
// @RequestMapping注解中的consume
private static final ConsumesRequestCondition EMPTY_CONSUMES = new ConsumesRequestCondition();
// @RequestMapping注解中的produce
private static final ProducesRequestCondition EMPTY_PRODUCES = new ProducesRequestCondition();
// 自定义条件
private static final RequestConditionHolder EMPTY_CUSTOM = new RequestConditionHolder(null);


@Nullable
private final String name;
@Nullable
private final PathPatternsRequestCondition pathPatternsCondition;
@Nullable
private final PatternsRequestCondition patternsCondition;
private final RequestMethodsRequestCondition methodsCondition;
private final ParamsRequestCondition paramsCondition;
private final HeadersRequestCondition headersCondition;
private final ConsumesRequestCondition consumesCondition;
private final ProducesRequestCondition producesCondition;
private final RequestConditionHolder customConditionHolder;
private final int hashCode;

简单实例图示
SpringMVC常见组件之HandlerMapping分析_ide_15

② HandlerMethod

这个类封装了处理器实例(Controller Bean)和 处理方法实例(Method)以及方法参数数组(MethodParameter[])。其内部成员变量为:

private final Object bean;
@Nullable
private final BeanFactory beanFactory;
private final Class<?> beanType;
private final Method method;
private final Method bridgedMethod;
private final MethodParameter[] parameters;
@Nullable
private HttpStatus responseStatus;
@Nullable
private String responseStatusReason;
@Nullable
private HandlerMethod resolvedFromHandlerMethod;
@Nullable
private volatile List<Annotation[][]> interfaceParameterAnnotations;
private final String description;

HandlerMethod实例图示
SpringMVC常见组件之HandlerMapping分析_子类_16
其有用对Method的应用,Method示例图示如下:
SpringMVC常见组件之HandlerMapping分析_spring_17

③ AbstractHandlerMethodMapping#MappingRegistry

MappingRegistry是AbstractHandlerMethodMapping内部类,内部维护了系列final类型的Map。用来存储​​"映射与目标"​​之间的关系,如下所示:

class MappingRegistry {

private final Map<T, MappingRegistration<T>> registry = new HashMap<>();

private final MultiValueMap<String, T> pathLookup = new LinkedMultiValueMap<>();

private final Map<String, List<HandlerMethod>> nameLookup = new ConcurrentHashMap<>();

private final Map<HandlerMethod, CorsConfiguration> corsLookup = new ConcurrentHashMap<>();

private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
//...
}

其提供了核心方法​​register​​​用来向​​pathLookup、nameLookup、corsLookup以及registry​​​放入数据。​​unregister​​​方法从​​pathLookup、nameLookup、corsLookup以及registry​​移除入数据。

我们可以通过下图直观感受其究竟是一个怎样的实例。

SpringMVC常见组件之HandlerMapping分析_spring_18

④ ApplicationContextAware与ApplicationObjectSupport

​ApplicationContextAware​​接口只提供了一个方法用来为当前上下文设置ApplicationContext 。

void setApplicationContext(ApplicationContext applicationContext) throws BeansException;

当bean实例化后,会遍历BeanPostProcessor调用其​​postProcessBeforeInitialization​​​进行后置处理。如​​ApplicationContextAwareProcessor​​​的​​postProcessBeforeInitialization​​​会判断bean是否是​​EnvironmentAware​​​、​​EmbeddedValueResolverAware​​​、​​ResourceLoaderAware​​​、​​ApplicationEventPublisherAware​​​、​​MessageSourceAware​​​、​​ApplicationContextAware​​​以及​​ApplicationStartupAware​​。如果是其中之一,则会调用对应的setXXX方法如setApplicationContext。

​ApplicationObjectSupport​​​实现了​​ApplicationContextAware​​​接口​​setApplicationContext​​​方法,如下图所示其提供了​​initApplicationContext​​方法供子类实现。

@Override
public final void setApplicationContext(@Nullable ApplicationContext context) throws BeansException {
if (context == null && !isContextRequired()) {
// Reset internal context state.
this.applicationContext = null;
this.messageSourceAccessor = null;
}
else if (this.applicationContext == null) {
// Initialize with passed-in context.
if (!requiredContextClass().isInstance(context)) {
throw new ApplicationContextException(
"Invalid application context: needs to be of type [" + requiredContextClass().getName() + "]");
}
this.applicationContext = context;
this.messageSourceAccessor = new MessageSourceAccessor(context);
// 这里是核心
initApplicationContext(context);
}
else {
// Ignore reinitialization if same context passed in.
if (this.applicationContext != context) {
throw new ApplicationContextException(
"Cannot reinitialize with different application context: current one is [" +
this.applicationContext + "], passed-in one is [" + context + "]");
}
}
}

在本文​​HandlerMapping​​​体系中,​​AbstractHandlerMapping​​​、​​AbstractDetectingUrlHandlerMapping​​​以及​​SimpleUrlHandlerMapping​​实现了这三个方法

故而当​​BeanNameUrlHandlerMapping​​​或者​​SimpleUrlHandlerMapping​​​实例化时,就会触发​​setApplicationContext​​方法,然后触发initApplicationContext方法。