本文基于spring 5.5.2.release
本文将分析HandlerMapping的另一个实现类:BeanNameUrlHandlerMapping,它的作用是将请求映射到bean名字为http请求路径的Controller对象,之后springmvc将请求转发给该对象处理。
本文将首先介绍BeanNameUrlHandlerMapping的继承结构,然后介绍创建该对象的代码、初始化代码,最后介绍如何查找Controller对象。
一、继承结构
BeanNameUrlHandlerMapping的继承结构还是挺复杂的,如果要研究每个继承类的作用需要花费很多篇幅,这里就不一一介绍了,本文只关注在处理请求过程中调用的方法。
从继承结构上我们看到,BeanNameUrlHandlerMapping实现了接口ApplicationContextAware,因此对象创建完后,会调用setApplicationContext()方法,该方法也就是上文所说的初始化方法。
二、对象创建
BeanNameUrlHandlerMapping对象是由WebMvcConfigurationSupport.beanNameHandlerMapping()方法创建的,创建的过程很简单:
@Bean
public BeanNameUrlHandlerMapping beanNameHandlerMapping(
@Qualifier("mvcConversionService") FormattingConversionService conversionService,
@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
BeanNameUrlHandlerMapping mapping = new BeanNameUrlHandlerMapping();
//顺序为2,它位于RequestMappingHandlerMapping之后进行遍历
mapping.setOrder(2);
//设置拦截器,在调用Controller对象前和后执行拦截器
mapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));
//CorsConfiguration用于处理http请求头
mapping.setCorsConfigurations(getCorsConfigurations());
return mapping;
}
三、初始化
在第一小节提到,初始化是调用setApplicationContext()方法,该方法是在父类ApplicationObjectSupport中实现的,方法里面会首先对拦截器做初始化,之后调用AbstractDetectingUrlHandlerMapping.detectHandlers()方法注册Controller对象。我们主要关注它是如何注册Controller对象的。下面看一下该方法代码:
protected void detectHandlers() throws BeansException {
//获取容器对象
ApplicationContext applicationContext = obtainApplicationContext();
//将容器中所有的bean对象名字组成数组
String[] beanNames = (this.detectHandlersInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, Object.class) :
applicationContext.getBeanNamesForType(Object.class));
//遍历每个bean对象
for (String beanName : beanNames) {
//下面这个方法是在BeanNameUrlHandlerMapping实现的,下面有该方法代码
String[] urls = determineUrlsForHandler(beanName);
if (!ObjectUtils.isEmpty(urls)) {
//注册Controller对象,Controller对象其实也就是Handler对象
//后面介绍该方法
registerHandler(urls, beanName);
}
}
//代码删减
}
//如果入参bean的名字或者其别名是以"/"开头,那么便表示该bean是一个Controller对象,
//可以处理http请求,该bean的名字便可以加入到方法返回值中
protected String[] determineUrlsForHandler(String beanName) {
List<String> urls = new ArrayList<>();
//检查bean对象名字是否以"/"开头
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()方法可以看出springmvc只检查bean名字是否是以“/”开头,只要是,就认为该bean可以处理http请求,所以在定义bean名字时一定注意这一点。
检查完bean名字后,调用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;
//lazyInitHandlers默认是false,表示是否懒初始化
if (!this.lazyInitHandlers && handler instanceof String) {
//下面逻辑是从容器中获取bean对象
//对该bean对象没有特殊要求,只要是singleton的就可以,这个对象也就是Controller对象
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 {
//将Controller对象注册到Map对象中,key是请求路径,value是Controller对象
this.handlerMap.put(urlPath, resolvedHandler);
}
}
}
注册Controller对象是将请求路径与Controller对象之间的对应关系保存到Map对象中。
到这里BeanNameUrlHandlerMapping的创建和初始化完成了,该对象内部保存了请求路径与Controller之间的对应关系。下面来看一下如何查找Controller。注意这里的Controller,也就是Handler。
四、查找Handler
当客户端发起http请求时,DispatcherServlet遍历各个HandlerMapping来查找处理该请求的Handler,对于BeanNameUrlHandlerMapping来说,查找逻辑是在AbstractUrlHandlerMapping类的lookupHandler()方法中处理的:
//urlPath是http请求路径
//request是http请求对象
protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
//根据请求路径从Map中直接查找Handler
Object handler = this.handlerMap.get(urlPath);
if (handler != null) {
//如果handler是字符串,表示handler是bean对象的名字
//还记得上文的lazyInitHandlers属性吗,如果该属性设置为true,那么这里得到的参数handler就是String的
if (handler instanceof String) {
String handlerName = (String) handler;
//根据bean名字从容器中得到对象
handler = obtainApplicationContext().getBean(handlerName);
}
validateHandler(handler, request);
//下面的方法用于创建HandlerExecutionChain对象
return buildPathExposingHandler(handler, urlPath, urlPath, null);
}
List<String> matchingPatterns = new ArrayList<>();
//如果根据请求路径无法精确匹配到Handler,那么遍历每个注册的路径,使用模糊匹配
//这里的匹配规则是正则表达式
for (String registeredPattern : this.handlerMap.keySet()) {
if (getPathMatcher().match(registeredPattern, urlPath)) {
matchingPatterns.add(registeredPattern);
}
else if (useTrailingSlashMatch()) {
if (!registeredPattern.endsWith("/") && getPathMatcher().match(registeredPattern + "/", urlPath)) {
matchingPatterns.add(registeredPattern + "/");
}
}
}
String bestMatch = null;
//如果找到多个匹配的路径,那么对这些路径排序,找到一个匹配度最高的
Comparator<String> patternComparator = getPathMatcher().getPatternComparator(urlPath);
if (!matchingPatterns.isEmpty()) {
matchingPatterns.sort(patternComparator);
if (logger.isTraceEnabled() && matchingPatterns.size() > 1) {
logger.trace("Matching patterns " + matchingPatterns);
}
bestMatch = matchingPatterns.get(0);
}
//bestMatch表示匹配度最高的路径,它对应的Controller便是最终要找的
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 + "]");
}
}
//如果handler是字符串,表示handler是bean对象的名字,那么便从容器中获得对应的对象
if (handler instanceof String) {
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
}
validateHandler(handler, request);
String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestMatch, urlPath);
//下面的逻辑是为拦截器准备的,作用未知
// 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, urlPath);
Map<String, String> decodedVars = getUrlPathHelper().decodePathVariables(request, vars);
uriTemplateVariables.putAll(decodedVars);
}
}
//下面的方法用于创建HandlerExecutionChain对象
return buildPathExposingHandler(handler, bestMatch, pathWithinMapping, uriTemplateVariables);
}
//未找到对应的Handler
return null;
}
lookupHandler()方法内容比较多,这里做一下总结:
- 首先根据http请求路径精确查找Handler,如果找到则创建HandlerExecutionChain对象返回;
- 如果上一步没有找到,那么遍历每个之前注册的路径,使用模糊匹配,匹配规则为正则表达式;
- 如果上一步找到多个匹配的路径,那么对这些路径排序,找到一个匹配度最高的,最简单的计算匹配度方法是按照长度,如果注册路径的长度越长,意味着匹配度越高;
- 根据找到的最优注册路径从Map对象中获得对应的bean对象,然后创建HandlerExecutionChain对象返回。
HandlerExecutionChain在上一篇文章《SpringMVC-详解HandlerMapping的前世今生之RequestMappingHandlerMapping》已经介绍了,这里不再介绍。
五、总结
本文介绍了BeanNameUrlHandlerMapping的对象创建、初始化,以及如何查找Handler。在本类中,Handler指的是bean对象,springmvc并没有对这个bean对象做特殊要求,只要是bean名字以“/”开头都可以处理http请求。但是在调用的时候,springmvc会进行检查,要求必须实现Controller接口。