Spring 源码解析 – SpringWeb请求映射解析


简介

基于上篇请求路径初步探索,了解到了一个请求到具体处理方法的大致路径,本篇就继续探索,看下路径是如何匹配到处理方法的

概览

基于上篇:Spring Web 请求初探

我们大致定位到了请求路径到处理方法的关键代码在类中:DispatcherServlet.java

具体的代码如下:

mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

mappedHandler 的获取就是关键了,下面我们就具体看下如何获取请求的mappedHandler的

源码解析

获取 mappedHandler

在类:DispatcherServlet.java 中,我们定位到 mappedHandler 获取的关键代码

// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {noHandlerFound(processedRequest, response);return;
}

通过上面的代码,我们可以看到是通过请求进行获取,我们还注意到了为null的情况,如下代码,大意就是经典的404

/**
* No handler found -> set appropriate HTTP response status.
* @param request current HTTP request
* @param response current HTTP response
* @throws Exception if preparing the response failed
*/
protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws Exception {if (pageNotFoundLogger.isWarnEnabled()) {pageNotFoundLogger.warn("No mapping for " + request.getMethod() + " " + getRequestUri(request));}if (this.throwExceptionIfNoHandlerFound) {throw new NoHandlerFoundException(request.getMethod(), getRequestUri(request),new ServletServerHttpRequest(request).getHeaders());}else {response.sendError(HttpServletResponse.SC_NOT_FOUND);}
}

我们进入 getHandler 函数,看看其具体处理逻辑,代码如下:

/**
* Return the HandlerExecutionChain for this request.
* <p>Tries all handler mappings in order.
* @param request current HTTP request
* @return the HandlerExecutionChain, or {@code null} if no handler could be found
*/
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {if (this.handlerMappings != null) {for (HandlerMapping mapping : this.handlerMappings) {HandlerExecutionChain handler = mapping.getHandler(request);if (handler != null) {return handler;}}}return null;
}

从上面代码大意可以看出:循环遍历一个关于请求匹配的Map,如果某个Handler匹配成功则返回

这个Map是如何初始化、具体包含哪些内容?

HandlerExecutionChain 具体是什么?

这部分疑问后面再看,感觉是个大工程,当前并不影响本篇的解析

请求与Mapper匹配

接着跟下去,到类:AbstractHandlerMethodMapping.java

我们来到了一个比较关键的处理,我们可以看到这里有相关的匹配逻辑,里面有很多的处理,有些目前都不知道有啥作用,后面查查资料后咱们再回过头来看看

 @Override@Nullablepublic final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {Object handler = getHandlerInternal(request);// 什么情况下会匹配到默认的Handler?if (handler == null) {handler = getDefaultHandler();}if (handler == null) {return null;}// 什么情况下获取到的Bean是一个String?// 需要从 obtainApplicationContext 中获取// 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 othersif (!ServletRequestPathUtils.hasCachedPath(request)) {initLookupPath(request);}HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);if (logger.isTraceEnabled()) {logger.trace("Mapped to " + handler);}else if (logger.isDebugEnabled() && !DispatcherType.ASYNC.equals(request.getDispatcherType())) {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;}

其中的 getHandlerExecutionChain 还挺可疑,感觉也是关键代码,给设置了一堆的我们熟悉的 Interceptors,这里做个标记,后面再探索下

 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匹配

下面就跟着断点来到了:AbstractHandlerMapping.java

看到下面的关键代码:在查找匹配的时候上了一个锁,进入具体的查找逻辑

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

我们进入类:AbstractHandlerMethodMapping.java 中,查看具体的处理逻辑

我们可以看到这里进行了一系列的匹配,如果为空还能取默认值,这个默认值是个啥?

并且还有多个匹配上的还不会报错的情况,瞬间感觉Web这块还是有很多门道啊,但这些细节后面我们再看看,继续看下匹配的逻辑

 @Nullableprotected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {List<Match> matches = new ArrayList<>();// 在这里获取到了相关的Handler,就是一个简单的Map取值(如后面的代码),那个Map如何初始化的得后面看看了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)) {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);}}

一个简单的Map取值(如后面的代码),那个Map如何初始化的得后面看看了

class MappingRegistry {private final MultiValueMap<String, T> pathLookup = new LinkedMultiValueMap<>();@Nullablepublic List<T> getMappingsByDirectPath(String urlPath) {return this.pathLookup.get(urlPath);}
}

到这里就匹配成功并返回了,但通过调试看到不是我们熟悉的类和方法

继续看看这行的具体处理逻辑:return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);

看看类 HandlerMethod 的一些方法和构造函数:

下面函数我们看到了属性的获取Bean

 public HandlerMethod createWithResolvedBean() {Object handler = this.bean;if (this.bean instanceof String) {Assert.state(this.beanFactory != null, "Cannot resolve bean name without BeanFactory");String beanName = (String) this.bean;handler = this.beanFactory.getBean(beanName);}return new HandlerMethod(this, handler);}

在其构造函数中,直接将Bean和Method进行了初始化,这个如何初始化的我们后面再看

 public HandlerMethod(Object bean, Method method) {Assert.notNull(bean, "Bean is required");Assert.notNull(method, "Method is required");this.bean = bean;this.beanFactory = null;this.beanType = ClassUtils.getUserClass(bean);this.method = method;this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);this.parameters = initMethodParameters();evaluateResponseStatus();this.description = initDescription(this.beanType, this.method);}

总结

本篇文章探索了下具体的请求如何匹配到处理方法的相关处理函数,了解到了核心逻辑就是遍历一个: handlerMappings

@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {if (this.handlerMappings != null) {for (HandlerMapping mapping : this.handlerMappings) {HandlerExecutionChain handler = mapping.getHandler(request);if (handler != null) {return handler;}}}return null;
}

后面的具体匹配逻辑也有很多值得探索的细节,但接下来更重要的是弄清楚这个: handlerMappings ,是如何初始化和加载的

下面的文章我们会继续探索

Spring 源码解析 -- SpringWeb请求映射解析相关推荐

  1. Spring源码解析 -- SpringWeb请求映射Map初始化

    简介 在上篇文章中,大致解析了Spring如何将请求路径与处理方法进行映射,但映射相关的初始化对于我们来说还是一团迷雾 本篇文章就来探索下,请求路径和处理方法的映射,是如何进行初始化的 概览 基于上篇 ...

  2. spring 源码分析(1)-xml文件解析

    我们在最开始接触spring的时候,看到不少书spring入门的例子如下 ApplicationContext atx = new ClassPathXmlApplicationContext(&qu ...

  3. spring源码分析06-spring配置类解析

    什么是spring配置类? 类上有注解:@Configuration .@Component.@ComponentScan.@Import.@ImportResource 或者类中的任意方法有@Bea ...

  4. Spring 源码第三弹!EntityResolver 是个什么鬼?

    上篇文章和小伙伴们说了 Spring 源码中 XML 文件的解析流程,本来可以继续往下走看加载核心类了,但是松哥还是希望能够慢一点,既然要学就学懂,在 XML 文件解析的过程中还涉及到一些其他的类和概 ...

  5. Spring源码分析之Bean的创建过程详解

    前文传送门: Spring源码分析之预启动流程 Spring源码分析之BeanFactory体系结构 Spring源码分析之BeanFactoryPostProcessor调用过程详解 本文内容: 在 ...

  6. 【Spring源码】4. 自己搞个标签?~自定义标签保姆级全过程(图解向,堆图预警)

    [Spring源码系列- IOC] 1 [Spring源码]0.安装Gradle环境 2 [Spring源码]1.下载与编译_pom relocation to an other version nu ...

  7. Spring源码解析 -- SpringWeb请求参数获取解析

    Spring源码解析 – SpringWeb请求参数获取解析 简介 在文章:Spring Web 请求初探中,我们看到最后方法反射调用的相关代码,本篇文章就探索其中的参数是如何从请求中获取的 概览 方 ...

  8. Spring 源码解析 -- SpringWeb过滤器Filter解析

    简介 在上几篇文章中探索了请求处理相关的代码,本篇开始探索请求处理前的一些操作代码,如Filter.本篇探索Filter初始化.请求处理等相关代码. 前言 说先简单的定义相关的测试代码: 启动类: i ...

  9. spring源码深度解析—Spring的整体架构和环境搭建

    概述 Spring是一个开放源代码的设计层面框架,他解决的是业务逻辑层和其他各层的松耦合问题,因此它将面向接口的编程思想贯穿整个系统应用.Spring是于2003 年兴起的一个轻量级的Java 开发框 ...

最新文章

  1. JSP_运维_JSP项目部署到server(适合0经验新手)
  2. matlab选择激发波长,【求助】怎么确定一个物质的激发波长和发射波长?
  3. Unity中那些事半功倍的好插件
  4. 【深度学习】CNN在大规模图像数据集上的应用(基于keras和MNIST)
  5. mysql防注入原理_MyBatis如何防止SQL注入
  6. JAVA多线程之UncaughtExceptionHandler——处理非正常的线程中止
  7. html ul左侧浮动,UL里的LI元素左浮动层一行显示时使其居中的方法
  8. Memcached入门指南
  9. js实现svg图形转存为图片下载
  10. 风口更需冷静 智能家居如何跨越鸿沟?
  11. YARN调度策略比较
  12. CSS3 之 flex
  13. 详解循环神经网络RNN(理论篇)
  14. python连接服务器informix_Python 用odbc连接Informix数据库 64位
  15. 汇编语言程序设计技巧详解(附例题)
  16. 密码技术学习(8.1)-数字证书简介
  17. android 墓碑日志,关于清明节扫墓的日记
  18. 不能同吃的食物组合(你知道吗?)
  19. 组合数算法的非递归实现
  20. IdentityServer3 v1文档

热门文章

  1. Nokia Lumia(WP7.5) 新手入门的摸索
  2. 数据库快照的工作方式
  3. 解决thrift: ···No such file or directory问题
  4. 如何解决Mac无法写入U盘的问题
  5. python虚拟环境解决不能执行脚本的问题
  6. Qt5 提示:无法启动此程序,计算机丢失Qt5Widgetsd.dll的解决方法
  7. 《乌镇互联网饭局图鉴》
  8. 解决Dev c++不能调试问题
  9. 搜索算法-三个简单的小问题
  10. 腾讯广告算法大赛 | 初赛第一名心得分享