简介
说到springmvc都有过使用过程,也都对格组件流转过程有所认识,但是一直没有特地看看requestMappinHandlerMapping组件,故今天深入探究下这个组件。
分析
先来看看这个组件全景图。
先不分析为何这么设计,先看看组件使用,从使用过程来探究设计原因,首先看看组件初始化。
从这里很容易看清,初始化主要过程就是容器实例化单实例化RequestMappingHandlerMapping时,会触发其InitializingBean特性,然后实现请求和handler的映射。具体的看一下AbstractHandlerMathhodMapping的一段代码。
public void register(T mapping, Object handler, Method method) {
this.readWriteLock.writeLock().lock();
try {
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
validateMethodMapping(handlerMethod, mapping);
this.mappingLookup.put(mapping, handlerMethod);
List<String> directUrls = getDirectUrls(mapping);
for (String url : directUrls) {
this.urlLookup.add(url, mapping);
}
String name = null;
if (getNamingStrategy() != null) {
name = getNamingStrategy().getName(handlerMethod, mapping);
addMappingName(name, handlerMethod);
}
CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
if (corsConfig != null) {
this.corsLookup.put(handlerMethod, corsConfig);
}
this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
}
finally {
this.readWriteLock.writeLock().unlock();
}
}
具体看下来就是5个写(存储)Map<T, HandlerMethod> mappingLookup,这里T(RequestMappingInfo),MultiValueMap<String, T> urlLookup,Map<String, List<HandlerMethod>> nameLookup,Map<HandlerMethod, CorsConfiguration> corsLookup,Map<T, MappingRegistration<T>> registry。到这mapping的映射存储大致搞清楚了,那剩下的部分就是如何读(获取)这个映射了,那获取Handler的入口就是DispatcherServlet。
可以看到HttpServletRequest处理流程从DispatherServlet开始,最终到AbstractHandlerMethodMapping去找HandlerMethod去处理,看看AbstractHandlerMethodMapping如何获取HandlerMethod。
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
List<Match> matches = new ArrayList<>();
//1获取RequestMappingInfo集合
List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
if (directPathMatches != null) {
addMatchingMappings(directPathMatches, matches, request);
}
if (matches.isEmpty()) {
addMatchingMappings(this.mappingRegistry.getMappings().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)) {
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();
}
}
request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
handleMatch(bestMatch.mapping, lookupPath, request);
return bestMatch.handlerMethod;
}
else {
return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
}
}
public List<T> getMappingsByUrl(String urlPath) {
return this.urlLookup.get(urlPath);
}
从这里就看到了从urlLookup中获取List<RequestMappingInfo>,然后找到最佳的HandlerMethod,最后包装成HandlerAdapter,最终执行HandlerMethod的Method的invoke业务代码也就执行完毕了。
到这里可以坐下小结了,handlerMapping初始化会detectHandlerMethods,检测到一个HandlerMethod就将它保存到urlLookup中,当处理请求的时候就通过名称到urlLookup中去找,最终执行业务代码。说到这里有一点需要补充一下,就是如何detectHandlerMethods,看看AbstractHandlerMethodMapping。
这里就很清楚了,就是遍历每一个controller的所有方法,将标注了@requestMapping的方法找到,然后去注册到requestMapping中。
总结
其实这里也就稍微提及了该组件的原理,深入理解还需每个细节具体研究,下面谈谈个人理解。
先从api设计角度谈谈这个组件,HandlerMapping规范了HttpServletRequest和HandlerExecutionChain的关系,AbstractHandlerMapping时HandlerMapping的基本实现并留下了扩展点getHandlerInternal,AbstractHandlerMethodMapping做了由HttpServletRequest获取HandlerMethod的实现,即解决了如何读的问题,AbstractHandlerMethodMapping同时实现了InitializingBean,做了HandlerMethods的初始化工作,令其子类RequestMappingHandlerMapping做好寻找目标方法(@RequestMapping标注的方法),并保存内部类MappingRegistry中,即解决了如何写的问题。
其次从servlet角度谈谈该组件,从servlet来说其接收到request请求后,要将请求派发给相应的handler来处理请求,结合到spring来说就是如何根据请求路径来路由到对应controller的某个Method的问题,那么在容器启动时我们做一个组件将请求路径和对应方法的映射保存起来,当处理请求时根据请求路径来获取该方法,这个问题就得以解决了,所以做了这个RequestMapping。