简介

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

本篇文章就来探索下,请求路径和处理方法的映射,是如何进行初始化的

概览

基于上篇文章:Spring 源码解析 – SpringWeb请求映射解析

本篇文章本来想早点写完,但一直卡着,没有找到想要的切入点,还好在周四左右定位到了相关的关键代码,初步探索到相关初始化的代码过程

接下来就展示这段时间的定位和解析过程,下面是自己这段时间探索历程:

  • 想定位 handlerMappings 的初始化,但没有定位请求URL和处理方法相关初始化的东西
  • 回过来头来看 handlerMappings ,看其有哪些东西,发现这个Map中并没有自定义的HelloWorld
  • 意识到关键的 RequestMappingHandlerMapping,跟踪发送只有在这个类型才成功匹配
  • 回顾上篇的请求映射解析,在 RequestMappingHandlerMapping 细致初始化相关的代码
  • 成功找到相关的路径和处理方法初始化的关键代码

接下来详细看下:

源码解析

初步探索初始化:误入歧途

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

@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 ,于是去跟踪了 handlerMappings 初始化过程

结果失败而归,没有找到自己想要的东西,并没有发现自定义类: HelloWorld 的相关东西

这块有很多的代码,里面也有很多的东西,但在这里就不展示出来了,感兴趣的老哥可自行探索

回顾请求映射查找匹配:幡然醒悟

探索 handlerMappings 无果,于是回到上面那段遍历处理

经过调试 handlerMappings 基本是固定的,包含下面的类:

  • this.handlerMappings

    • RequestMappingHandlerMapping
    • BeanNameUrlHandlerMapping
    • RouterFunctionMapping
    • SimpleUrlHandlerMapping
    • WelcomePageHandlerMapping

而匹配的成功的是: RequestMappingHandlerMapping ,其返回了我们想要的处理方法 HelloWorld

调试中很疑惑为啥初期要初始化这几个了,并且再套了一层请求匹配,目前掌握的知识还不足于破解,只能后面再探索了

于是开始梳理 RequestMappingHandlerMapping 的请求匹配,在下面的一段关键代码匹配成功了:

    # AbstractHandlerMethodMapping.java@Nullableprotected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {List<Match> matches = new ArrayList<>();// 在这里拿到了匹配结果List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath);if (directPathMatches != null) {addMatchingMappings(directPathMatches, matches, request);}if (matches.isEmpty()) {addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request);}.......}

在上面的代码中就匹配成功了,其中匹配的方法很简单粗暴:

    # AbstractHandlerMethodMapping.java -- MappingRegistry@Nullablepublic List<T> getMappingsByDirectPath(String urlPath) {return this.pathLookup.get(urlPath);}

于是关键点到了 this.mappingRegistry 的初始化,找到初始化的代码,打上断点

期间以为是在类:AbstractHandlerMethodMapping 中进行的初始的,在下面的函数打上了断点:

    # AbstractHandlerMethodMapping.javapublic void setPatternParser(PathPatternParser patternParser) {Assert.state(this.mappingRegistry.getRegistrations().isEmpty(),"PathPatternParser must be set before the initialization of " +"request mappings through InitializingBean#afterPropertiesSet.");super.setPatternParser(patternParser);}public void registerMapping(T mapping, Object handler, Method method) {if (logger.isTraceEnabled()) {logger.trace("Register \"" + mapping + "\" to " + method.toGenericString());}this.mappingRegistry.register(mapping, handler, method);}

但一直进不去,于是直接在其定义的内部类中: MappingRegistry 中进行寻找,并成功定位到想要的关键代码

请求映射关键代码定位:柳暗花明

阅读类: MappingRegistry 的相关代码,发现下面的方法和可以,我们直接打上断点,重启程序:

发现了前面的: this.pathLookup 的相关添加操作

    public void register(T mapping, Object handler, Method method) {this.readWriteLock.writeLock().lock();try {HandlerMethod handlerMethod = createHandlerMethod(handler, method);validateMethodMapping(handlerMethod, mapping);Set<String> directPaths = AbstractHandlerMethodMapping.this.getDirectPaths(mapping);for (String path : directPaths) {// 这段代码是关键this.pathLookup.add(path, mapping);}String name = null;if (getNamingStrategy() != null) {name = getNamingStrategy().getName(handlerMethod, mapping);addMappingName(name, handlerMethod);}CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);if (corsConfig != null) {corsConfig.validateAllowCredentials();this.corsLookup.put(handlerMethod, corsConfig);}this.registry.put(mapping,new MappingRegistration<>(mapping, handlerMethod, directPaths, name, corsConfig != null));}finally {this.readWriteLock.writeLock().unlock();}}

应用重启后,果然顺利来到我们打上的断点处,通过分析调用栈,我们确实找到了请求映射的关键代码

我们将调用栈从下网上分析查看:

应用启动相关

开始就是熟悉Spring启动相关,这些代码相信大家尝试阅读源码的时候读过很多遍了

跟踪发现在: DefaultListableBeanFactory.class 的 preInstantiateSingletons 方法中个,一大段嵌套循环,心想这段代码目前能优化吗?

    public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {return (new SpringApplication(primarySources)).run(args);}public static void main(String[] args) throws Exception {run(new Class[0], args);}public ConfigurableApplicationContext run(String... args) {......try {......this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);// 从下面这个进入this.refreshContext(context);this.afterRefresh(context, applicationArguments);......} catch (Throwable var10) {......this.handleRunFailure(context, var10, listeners);throw new IllegalStateException(var10);}......}public void refresh() throws BeansException, IllegalStateException {synchronized(this.startupShutdownMonitor) {.......try {this.postProcessBeanFactory(beanFactory);StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");this.invokeBeanFactoryPostProcessors(beanFactory);this.registerBeanPostProcessors(beanFactory);beanPostProcess.end();this.initMessageSource();this.initApplicationEventMulticaster();this.onRefresh();this.registerListeners();// 从这里进入this.finishBeanFactoryInitialization(beanFactory);this.finishRefresh();} catch (BeansException var10) {} finally {......}......}}

RequestMappingHandlerMapping 相关初始化

继续跟踪下面的,看到了属性的CreateBean和afterPropertiesSet

    # AbstractAutowireCapableBeanFactory.classprotected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException {......try {// 这里初始化了 RequestMappingHandlerMappingbeanInstance = this.doCreateBean(beanName, mbdToUse, args);if (this.logger.isTraceEnabled()) {this.logger.trace("Finished creating instance of bean '" + beanName + "'");}return beanInstance;} catch (ImplicitlyAppearedSingletonException | BeanCreationException var7) {throw var7;} catch (Throwable var8) {throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", var8);}}# AbstractAutowireCapableBeanFactory.classprotected void invokeInitMethods(String beanName, Object bean, @Nullable RootBeanDefinition mbd) throws Throwable {boolean isInitializingBean = bean instanceof InitializingBean;if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {if (this.logger.isTraceEnabled()) {this.logger.trace("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");}if (System.getSecurityManager() != null) {try {AccessController.doPrivileged(() -> {((InitializingBean)bean).afterPropertiesSet();return null;}, this.getAccessControlContext());} catch (PrivilegedActionException var6) {throw var6.getException();}} else {// 这里进入请求映射的相关操作((InitializingBean)bean).afterPropertiesSet();}}......}

请求映射初始化

继续跟踪下去,看看了循环遍历Controllers相关的代码(还有很多细节没搞清,后面再继续了,先梳理主线)

    # AbstractHandlerMethodMapping.java@Overridepublic void afterPropertiesSet() {// 初始化请求映射initHandlerMethods();}protected void initHandlerMethods() {// 遍历所有的自定义的Controllers,后面自己又定义了一个Controllersfor (String beanName : getCandidateBeanNames()) {if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {// 在这里看到了我们定义的HelloWorldprocessCandidateBean(beanName);}}handlerMethodsInitialized(getHandlerMethods());}protected String[] getCandidateBeanNames() {return (this.detectHandlerMethodsInAncestorContexts ?BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) :obtainApplicationContext().getBeanNamesForType(Object.class));}

继续跟踪下去,看到了下面的获取类中具体请求路径相关的代码,并且到了具体的初始化请求映射的代码

    # AbstractHandlerMethodMapping.javaprotected 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)) {// 得到Controller Bean后的入口detectHandlerMethods(beanName);}}protected void detectHandlerMethods(Object handler) {Class<?> handlerType = (handler instanceof String ?obtainApplicationContext().getType((String) handler) : handler.getClass());if (handlerType != null) {// 处理得到所有的Controllers methodClass<?> 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));}else if (mappingsLogger.isDebugEnabled()) {mappingsLogger.debug(formatMappings(userType, methods));}methods.forEach((method, mapping) -> {Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);// 注册registerHandlerMethod(handler, invocableMethod, mapping);});}}public void register(T mapping, Object handler, Method method) {this.readWriteLock.writeLock().lock();try {HandlerMethod handlerMethod = createHandlerMethod(handler, method);validateMethodMapping(handlerMethod, mapping);Set<String> directPaths = AbstractHandlerMethodMapping.this.getDirectPaths(mapping);for (String path : directPaths) {// 映射添加this.pathLookup.add(path, mapping);}}finally {this.readWriteLock.writeLock().unlock();}}

总结

经过一段时间的探索的整理,我们终于得到了大致的请求路径映射初始化的代码

  • 1.应用启动时,初始化:RequestMappingHandlerMapping
  • 2.RequestMappingHandlerMapping 中请求路径初始化

经过调试,我们还发现虽然 RequestMappingHandlerMapping 是一开始就初始化了,但加载到 handlerMappings 是第一次请求的时候才加载进去的

本篇虽然得到了大致的请求路径初始化的代码,但其中有很多细节是值得探索的,比如Bean中Method的处理

之前自己写过一些DI和Web相关的Demo,停在了Servlet,卡在了请求映射初始化和匹配,这个给了我一些思路,后面详细看看这块代码,完善下之前的Demo

参考链接

  • Spring 源码解析系列

Spring源码解析 -- SpringWeb请求映射Map初始化相关推荐

  1. Spring 源码解析 -- SpringWeb请求映射解析

    Spring 源码解析 – SpringWeb请求映射解析 简介 基于上篇请求路径初步探索,了解到了一个请求到具体处理方法的大致路径,本篇就继续探索,看下路径是如何匹配到处理方法的 概览 基于上篇:S ...

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

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

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

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

  4. Spring MVC源码解析——HandlerMapping(处理器映射器)

    Sping MVC 源码解析--HandlerMapping处理器映射器 1. 什么是HandlerMapping 2. HandlerMapping 2.1 HandlerMapping初始化 2. ...

  5. Spring源码解析:自定义标签的解析过程

    2019独角兽企业重金招聘Python工程师标准>>> spring version : 4.3.x Spring 中的标签分为默认标签和自定义标签两类,上一篇我们探究了默认标签的解 ...

  6. Spring源码解析【完整版】--【bilibili地址:https://www.bilibili.com/video/BV1oW41167AV】

    [本文为bilibili视频雷丰阳的Spring源码解析的完整版总结文章,其中文章前面大部分为他人博文的搬运,后面补充了其未总结的部分] 一.Java的注解 1. 注解的概念 注释:用文字描述程序,给 ...

  7. Spring源码解析 - AbstractBeanFactory 实现接口与父类分析

    2019独角兽企业重金招聘Python工程师标准>>> 我们先来看类图吧: 除了BeanFactory这一支的接口,AbstractBeanFactory主要实现了AliasRegi ...

  8. 源码解析:Spring源码解析笔记(五)接口设计总览

    本文由colodoo(纸伞)整理 QQ 425343603 Java学习交流群(717726984) Spring解析笔记 启动过程部分已经完成,对启动过程源码有兴趣的朋友可以作为参考文章. 源码解析 ...

  9. 人人都能看懂的Spring源码解析,Spring如何解决循环依赖

    人人都能看懂的Spring源码解析,Spring如何解决循环依赖 原理解析 什么是循环依赖 循环依赖会有什么问题? 如何解决循环依赖 问题的根本原因 如何解决 为什么需要三级缓存? Spring的三级 ...

最新文章

  1. WebService大讲堂之Axis2(5):会话(Session)管理
  2. Easyx项目小合集
  3. 成为Java流大师–第4部分:数据库流
  4. 下载OpenCV神经网络模型及pycharm中 argparse的参数设置
  5. 《构建之法》(第四、十七章)读书笔记
  6. 24. 练习定义几种指针及数组
  7. vb combox获取选定index_Python-新闻评论获取
  8. Netty-1-一个简单的服务器实例
  9. html几个数据包,报文和数据包的区别
  10. 等级VIN行业调研报告 - 市场现状分析与发展前景预测(2021-2027年)
  11. Java定时器quartz停止
  12. VMware资源集合,分享一波
  13. python 自动加载_【python】命令行下自动加载模块和tab补全功能
  14. matlab调用com组件出现错误求解决,matlab在生成COM组件编译时出现错误
  15. 紫光华宇拼音输入法V6 beta 3
  16. PHP手册-use关键字
  17. NOTE_网络存储-3 by 张冬
  18. 关于开发语言个人随想
  19. 视觉设计需要学什么?
  20. IT技术人员的六大方向

热门文章

  1. CSS3新增UI样式
  2. 解决ubuntu下的firefox无法在线播放音频和视频的问题
  3. Handshake failed due to invalid Upgrade header: null 解决方案
  4. 【干货】2021微信生态下的营销洞察.pdf(附下载链接)
  5. 【白皮书分享】2020中国房地产白皮书.pdf(附下载链接)
  6. 程序员面试——C++工程师面试大全第一部分
  7. 万物皆可Graph | 当信息检索遇上图神经网络
  8. 2022年 预训练的下一步是什么?
  9. POJ2931不平等博弈
  10. TensorFlow2.0:单层感知机梯度计算