之前分析过SpringMVC中的DispatcherServlet,分析了SpringMVC处理请求的过程。但忽略了一些DispatcherServlet协助请求处理的组件,例如SpringMVC中的HandlerMappingHandlerAdapterViewResolvers等等。

HandlerMappings

HandlerMappingsDispathServlet中主要作用是为请求的urlpath匹配对应的Controller,建立一个映射关系,根据请求查找HandlerInterceptorHandlerMappings将请求传递到HandlerExecutionChain上,HandlerExecutionChain包含了一个能够处理该请求的处理器,还可以包含拦截改请求的拦截器。

在没有处理器映射相关配置情况下,DispatcherServlet会为你创建一个BeanNameUrlHandlerMapping作为默认映射的配置。在DispatchServlet.properties文件中对于HandlerMapping的默认配置是:

org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping

HandlerMapping的配置策略一般分为配置式BeanNameUrlHandlerMapping和注解式DefaultAnnotationHandlerMapping。不过DefaultAnnotationHandlerMapping已经被放弃了,取代它的是RequestMappingHandlerMapping,不知道为啥SpringMVC这个默认配置尚未做修改。

AbstractHandlerMapping

AbstractHandlerMappingHandlerMapping的抽象实现,是所有HandlerMapping实现类的父类。

AbstractHandlerMapping的作用是是为了初始化InterceptorsAbstractHandlerMapping重写了WebApplicationObjectSupportinitApplicationContext方法。

    protected void initApplicationContext() throws BeansException {
extendInterceptors(this.interceptors);
detectMappedInterceptors(this.adaptedInterceptors);
initInterceptors();
}
  • extendInterceptors方法,Springmvc并没有做出具体实现,这里留下一个拓展,子类可以重写这个模板方法,为子类添加或者修改Interceptors

  • detectMappedInterceptors方法将SpringMVC容器中所有MappedInterceptor类的bean添加到adaptedInterceptors中。

  • 最后调用initInterceptors初始化拦截器。遍历interceptorsWebRequestInterceptorHandlerInterceptor类型的拦截器添加到adaptedInterceptors中。

HandlerMapping通过getHandler方法来获取请求的处理器Handler和拦截器Interceptor。在getHandlerExecutionChain方法中将遍历之前初始化的adaptedInterceptors,为当前的请求选择对应的MappedInterceptorsadaptedInterceptors

AbstractUrlHandlerMapping

AbstractUrlHandlerMapping

AbstractUrlHandlerMapping继承于AbstractHandlerMapping,它是通过URL来匹配具体的HandlerAbstractUrlHandlerMapping维护一个handlerMap来存储UrlHandler的映射关系。

AbstractUrlHandlerMapping重写了AbstractHandlerMapping类中的getHandlerInternal方法。HandlerMapping通过getHandler方法,就会调用这里的getHandlerInternal方法来获取HandlergetHandlerInternal方法中关键调用lookupHandler方法去获取handler

    protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
// Direct match?
Object handler = this.handlerMap.get(urlPath);
if (handler != null) {
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = getApplicationContext().getBean(handlerName);
}
validateHandler(handler, request);
return buildPathExposingHandler(handler, urlPath, urlPath, null);
} // Pattern match?
List<String> matchingPatterns = new ArrayList<String>();
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()) {
Collections.sort(matchingPatterns, patternComparator);
if (logger.isDebugEnabled()) {
logger.debug("Matching patterns for request [" + urlPath + "] are " + 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 = getApplicationContext().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<String, String>();
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);
}
}
if (logger.isDebugEnabled()) {
logger.debug("URI Template variables for request [" + urlPath + "] are " + uriTemplateVariables);
}
return buildPathExposingHandler(handler, bestMatch, pathWithinMapping, uriTemplateVariables);
} // No handler found...
return null;
}
  • 首先调用lookupHandler方法来获取handler。在lookupHandler方法中,先通过URLhandlerMap查找是否有合适的handler
  • 如果没有获取到handler,遍历handlerMap利用正则匹配的方法,找到符合要求的handlers(有可能是多个)。
  • 正则匹配是采用Ant风格,将会通过排序筛选出一个匹配程度最高的Handler
  • 最后调用buildPathExposingHandler方法构建一个handler,添加PathExposingHandlerInterceptorUriTemplateVariablesHandlerInterceptor两个拦截器并返回。

上面介绍获取handler的过程中,会先从handlerMap查找。下面看一下handlerMap是如何初始化的。AbstractUrlHandlerMapping是通过registerHandler初始化handlerMap的。AbstractUrlHandlerMapping共有两个registerHandler方法。分别是注册多个url到一个handler和注册一个url到一个handler。首先判断handlerMap是否有此handler。如果存在的话,判断是否一致,不一致则抛出异常,如果不存在的话,如果url//*,则,返回root handlerdefault handler,如果不是将添加到handlerMap中。

SimpleUrlHandlerMapping

SimpleUrlHandlerMapping继承于AbstractUrlHandlerMappingSimpleUrlHandlerMapping重写了父类AbstractHandlerMapping中的初始化方法initApplicationContext。在initApplicationContext方法中调用registerHandlers方法。

    protected void registerHandlers(Map<String, Object> urlMap) throws BeansException {
if (urlMap.isEmpty()) {
logger.warn("Neither 'urlMap' nor 'mappings' set on SimpleUrlHandlerMapping");
}
else {
for (Map.Entry<String, Object> entry : urlMap.entrySet()) {
String url = entry.getKey();
Object handler = entry.getValue();
// 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是否以/开头,如果不是,默认补齐/,确保所有的url都是以/开头,然后依次调用父类的registerHandler方法注册到AbstractUrlHandlerMapping中的handlerMap

在使用SimpleUrlHandlerMapping时,需要在注册的时候配置其urlmap否则会抛异常。

AbstractDetectingUrlHandlerMapping

AbstractDetectingUrlHandlerMapping类继承于AbstractUrlHandlerMapping类,重写了initApplicationContext方法,在initApplicationContext方法中调用了detectHandlers方法。

    protected void detectHandlers() throws BeansException {
if (logger.isDebugEnabled()) {
logger.debug("Looking for URL mappings in application context: " + getApplicationContext());
}
String[] beanNames = (this.detectHandlersInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
getApplicationContext().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);
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Rejected bean name '" + beanName + "': no URL paths identified");
}
}
}
}

获取所有容器的beanNames,遍历所有的beanName,调用determineUrlsForHandler方法解析url,这里的determineUrlsForHandler也是运用了模板方法设计模式,具体的实现在其子类中,如果解析到子类,将注册到父类的handlerMap中。

BeanNameUrlHandlerMapping

BeanNameUrlHandlerMapping类的类图大致如下:

BeanNameUrlHandlerMapping类继承于AbstractDetectingUrlHandlerMapping类。重写了父类中的determineUrlsForHandler方法。

    protected String[] determineUrlsForHandler(String beanName) {
List<String> urls = new ArrayList<String>();
if (beanName.startsWith("/")) {
urls.add(beanName);
}
String[] aliases = getApplicationContext().getAliases(beanName);
for (String alias : aliases) {
if (alias.startsWith("/")) {
urls.add(alias);
}
}
return StringUtils.toStringArray(urls);
}

其通过beanName解析Url规则也很简单,判断beanName是否以/开头。

BeanNameUrlHandlerMappingSpringMVC的默认映射配置。

AbstractHandlerMethodMapping

通常我们也习惯于用@Controller@Re questMapping来定义HandlerAbstractHandlerMethodMapping可以将method作为Handler来使用。

AbstractHandlerMethodMapping实现了InitializingBean接口,实现了afterPropertiesSet方法。当容器启动的时候会调用initHandlerMethods注册委托handler中的方法。


public void afterPropertiesSet() {
initHandlerMethods();
} protected void initHandlerMethods() {
if (logger.isDebugEnabled()) {
logger.debug("Looking for request mappings in application context: " + getApplicationContext());
}
String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
getApplicationContext().getBeanNamesForType(Object.class)); for (String beanName : beanNames) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
Class<?> beanType = null;
try {
beanType = getApplicationContext().getType(beanName);
}
catch (Throwable ex) {
// An unresolvable bean type, probably from a lazy bean - let's ignore it.
if (logger.isDebugEnabled()) {
logger.debug("Could not resolve target class for bean with name '" + beanName + "'", ex);
}
}
if (beanType != null && isHandler(beanType)) {
detectHandlerMethods(beanName);
}
}
}
handlerMethodsInitialized(getHandlerMethods());
}

initHandlerMethods方法中,做了以下工作:

  • 首先通过BeanFactoryUtils扫描应用上下文,获取所有的bean
  • 遍历所有的beanName,调用isHandler方法判断是目标bean是否包含@Controller@RequestMapping注解。
  • 对于带有@Controller@RequestMapping注解的类,调用detectHandlerMethods委托处理,获取所有的method,并调用registerHandlerMethod注册所有的方法。

detectHandlerMethods方法负责将Handler保存到Map中。

    protected void detectHandlerMethods(final Object handler) {
// 获取handler的类型
Class<?> handlerType = (handler instanceof String ?
getApplicationContext().getType((String) handler) : handler.getClass());
final Class<?> userType = ClassUtils.getUserClass(handlerType); Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
new MethodIntrospector.MetadataLookup<T>() {
@Override
public T inspect(Method method) {
try {
return getMappingForMethod(method, userType);
}
catch (Throwable ex) {
throw new IllegalStateException("Invalid mapping on handler class [" +
userType.getName() + "]: " + method, ex);
}
}
}); if (logger.isDebugEnabled()) {
logger.debug(methods.size() + " request handler methods found on " + userType + ": " + methods);
}
for (Map.Entry<Method, T> entry : methods.entrySet()) {
Method invocableMethod = AopUtils.selectInvocableMethod(entry.getKey(), userType);
T mapping = entry.getValue();
registerHandlerMethod(handler, invocableMethod, mapping);
}
}

selectMethods方法中重写了MetadataLookup中的inspect方法,inspect方法中调用了子类RequestMappingHandlerMapping实现了getMappingForMethod模板方法,用于构建RequestMappingInfo

public static <T> Map<Method, T> selectMethods(Class<?> targetType, final MetadataLookup<T> metadataLookup) {
final Map<Method, T> methodMap = new LinkedHashMap<Method, T>();
Set<Class<?>> handlerTypes = new LinkedHashSet<Class<?>>();
Class<?> specificHandlerType = null; if (!Proxy.isProxyClass(targetType)) {
handlerTypes.add(targetType);
specificHandlerType = targetType;
}
handlerTypes.addAll(Arrays.asList(targetType.getInterfaces())); for (Class<?> currentHandlerType : handlerTypes) {
final Class<?> targetClass = (specificHandlerType != null ? specificHandlerType : currentHandlerType); ReflectionUtils.doWithMethods(currentHandlerType, new ReflectionUtils.MethodCallback() {
@Override
public void doWith(Method method) {
Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
T result = metadataLookup.inspect(specificMethod);
if (result != null) {
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
if (bridgedMethod == specificMethod || metadataLookup.inspect(bridgedMethod) == null) {
methodMap.put(specificMethod, result);
}
}
}
}, ReflectionUtils.USER_DECLARED_METHODS);
} return methodMap;
}

selectMethods通过反射获取所有的方法,重写了doWith方法,将handler中的method和请求对应的RequestMappingInfo保存到methodMap中。

最终detectHandlerMethods将遍历这个methodMap,调用registerHandlerMethod注册HandlerMethodMappingRegistry

AbstractHandlerMethodMapping类中,有个内部类MappingRegistry,用来存储mapping和 handler methods注册关系,并提供了并发访问方法。

AbstractHandlerMethodMapping通过getHandlerInternal来为一个请求选择对应的handler

    protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
// 根据request获取对应的urlpath
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
if (logger.isDebugEnabled()) {
logger.debug("Looking up handler method for path " + lookupPath);
}
// 获取读锁
this.mappingRegistry.acquireReadLock();
try {
// 调用lookupHandlerMethod方法获取请求对应的HandlerMethod
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
if (logger.isDebugEnabled()) {
if (handlerMethod != null) {
logger.debug("Returning handler method [" + handlerMethod + "]");
}
else {
logger.debug("Did not find handler method for [" + lookupPath + "]");
}
}
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}
finally {
this.mappingRegistry.releaseReadLock();
}
}

lookupHandlerMethod的具体实现如下:

protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
List<Match> matches = new ArrayList<Match>();
// 通过lookupPath获取所有匹配到的path
List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
if (directPathMatches != null) {
// 将匹配条件添加到matches
addMatchingMappings(directPathMatches, matches, request);
}
if (matches.isEmpty()) {
// 如果没有匹配条件,将所有的匹配条件都加入matches
addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
} if (!matches.isEmpty()) {
Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
Collections.sort(matches, comparator);
if (logger.isTraceEnabled()) {
logger.trace("Found " + matches.size() + " matching mapping(s) for [" +
lookupPath + "] : " + matches);
}
// 选取排序后的第一个作为最近排序条件
Match bestMatch = matches.get(0);
if (matches.size() > 1) {
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();
throw new IllegalStateException("Ambiguous handler methods mapped for HTTP path '" +
request.getRequestURL() + "': {" + m1 + ", " + m2 + "}");
}
}
// 将lookupPath设为请求request的PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE属性
handleMatch(bestMatch.mapping, lookupPath, request);
return bestMatch.handlerMethod;
}
else {
return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
}
}

整个过程以Match作为载体,Match是个内部类,封装了匹配条件和handlerMethod两个属性,默认的实现是将lookupPath设置为请求的属性。

总结

本文从源码角度上分析了HandlerMapping的各种实现。主要功能是为请求找到合适的handlerinterceptors,并组合成HandlerExecutionChain。查找handler的过程通过getHandlerInternal方法实现,每个子类都其不同的实现。

所有的HandlerMapping的实现都继承于AbstarctHandlerMappingAbstarctHandlerMapping主要作用是完成拦截器的初始化工作。而通过AbstarctHandlerMapping又衍生出两个系列,AbstractUrlHandlerMappingAbstractHandlerMethodMapping

AbstractUrlHandlerMapping也有很多子类的实现,如SimpleUrlHandlerMappingAbstractDetectingUrlHandlerMapping。总体来说,AbstractUrlHandlerMapping需要用到一个保存urlhandler的对应关系的mapmap的初始化工作由子类实现。不同的子类会有自己的策略,可以在配置文件中注册,也可以在spring容器中找。

AbstractHandlerMethodMapping系列则通常用于注解的方法,解析包含@Controller或者@RequestMapping注解的类,建立urlmethod的直接对应关系,这也是目前使用最多的一种方式。

SpringMVC源码分析--HandlerMappings的更多相关文章

  1. springMVC源码分析--HandlerMapping(一)

    HandlerMapping的工作就是为每个请求找到合适的请求找到一个处理器handler,其实现机制简单来说就是维持了一个url到Controller关系的Map结构,其提供的实际功能也是根据req ...

  2. SpringMVC源码分析--容器初始化(五)DispatcherServlet

    上一篇博客SpringMVC源码分析--容器初始化(四)FrameworkServlet我们已经了解到了SpringMVC容器的初始化,SpringMVC对容器初始化后会进行一系列的其他属性的初始化操 ...

  3. [心得体会]SpringMVC源码分析

    1. SpringMVC (1) springmvc 是什么? 前端控制器, 主要控制前端请求分配请求任务到service层获取数据后反馈到springmvc的view层进行包装返回给tomcat, ...

  4. 8、SpringMVC源码分析(3):分析ModelAndView的形成过程

    首先,我们还是从DispatcherServlet.doDispatch(HttpServletRequest request, HttpServletResponse response) throw ...

  5. 7、SpringMVC源码分析(2):分析HandlerAdapter.handle方法,了解handler方法的调用细节以及@ModelAttribute注解

    从上一篇 SpringMVC源码分析(1) 中我们了解到在DispatcherServlet.doDispatch方法中会通过 mv = ha.handle(processedRequest, res ...

  6. springMVC源码分析--ViewNameMethodReturnValueHandler返回值处理器(三)

    之前两篇博客springMVC源码分析--HandlerMethodReturnValueHandler返回值解析器(一)和springMVC源码分析--HandlerMethodReturnValu ...

  7. springMVC源码分析--HandlerMethodReturnValueHandlerComposite返回值解析器集合(二)

    在上一篇博客springMVC源码分析--HandlerMethodReturnValueHandler返回值解析器(一)我们介绍了返回值解析器HandlerMethodReturnValueHand ...

  8. springMVC源码分析--RequestParamMethodArgumentResolver参数解析器(三)

    之前两篇博客springMVC源码分析--HandlerMethodArgumentResolver参数解析器(一)和springMVC源码解析--HandlerMethodArgumentResol ...

  9. springMVC源码分析--访问请求执行ServletInvocableHandlerMethod和InvocableHandlerMethod

    在之前一篇博客中springMVC源码分析--RequestMappingHandlerAdapter(五)我们已经简单的介绍到具体请求访问的执行某个Controller中的方法是在RequestMa ...

随机推荐

  1. mybatis遍历map

    mytabis是可以遍历map的,试过很多其他的方法都不行,最终使用如下方法是可以的: 1.mapper.java如下(注意要加@Param注解,否则不行,我就在这里折腾了不少时间): int upd ...

  2. python函数式编程-偏向函数

    Python的functools模块提供了很多有用的功能,其中一个就是偏函数(Partial function).要注意,这里的偏函数和数学意义上的偏函数不一样. 在介绍函数参数的时候,我们讲到,通过 ...

  3. SecureCRT进行端口转发

    总共3台机器:my电脑.跳转机器(外网).内网服务器. ​ 首先配置至跳板机(150.236.223.72:22)的连接: ​ 配置完成后选择Connect连接至跳板机,输入密码后可选择“Save p ...

  4. Goodbye Microservices: From 100s of problem children to 1 superstar

    https://segment.com/blog/goodbye-microservices/ Unless you’ve been living under a rock, you probably ...

  5. aiops相关

    AIOPS的能力框架 AIOps平台能力体系 AIOps 常见应用场景 按照时间来分 AIOPS实施的关键技术 1.数据采集(硬件,业务指标等) 2.数据预处理(特征工程) 3.数据可视化 4.数据存 ...

  6. jQuery隐藏和显示从上往下的实现方法

    jquery 显示隐藏方法实现动画效果 方向 显示 隐藏 左上角到右下角 show() hide() 垂直向下 slideDown() slideUp() 水平与垂直两个方向 toggle() 垂直向 ...

  7. 007_硬件基础电路_RC复位电路中二极管的作用

    --------------------- 作者:碎碎思 来源:CSDN 原文:https://blog.csdn.net/Pieces_thinking/article/details/781110 ...

  8. 报警提示 System.NullReferenceException:“未将对象引用设置到对象的实例。

    System.NullReferenceException:“未将对象引用设置到对象的实例.是就因为Session在记录到服务器时,没有添加  IRequiresSessionState 所以运行时回 ...

  9. 如何利用Wireshark解密SSL和TLS流量

    如何利用Wireshark解密SSL和TLS流量https://support.citrix.com/article/CTX135121 1.有server端的private key,直接在wires ...

  10. 【luogu1251】餐巾计划问题--网络流建模,费用流

    题目描述 一个餐厅在相继的 N 天里,每天需用的餐巾数不尽相同.假设第 iii 天需要 ri​块餐巾( i=1,2,...,N).餐厅可以购买新的餐巾,每块餐巾的费用为 p 分;或者把旧餐巾送到快洗部 ...