SpringBoot源码深度剖析
@SpringBootApplication注解和new SpringApplication().run()方法深度解密
一、依赖管理
本次源码剖析用的springboot的版本是2.2.4.RELEASE
首先要提出两个问题:
问题:(1)为什么导入dependency时不需要指定版本?
问题:(2)spring-boot-starter-parent父依赖启动器的主要作用是进行版本统一管理,那么项目运
行依赖的JAR包是从何而来的?
那么现在我们带着这两个问题来看代码:
问题:(1)为什么导入dependency时不需要指定版本?
1.spring-boot-starter-parent依赖
在项目的pom.xml文件中找到spring-boot-starter-parent依赖,如下:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
上述代码中,将spring-boot-starter-parent依赖作为Spring Boot项目的统一父项目依赖管理,并将项目版本号统一为2.2.4.RELEASE,该版本号根据实际开发需求是可以修改的。
使用“Ctrl+鼠标左键”进入并查看spring-boot-starter-parent底层源文件,发现spring-boot-starter-parent的底层有一个父依赖spring-boot-dependencies,核心代码具体如下:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${revision}</version>
<relativePath>../../spring-boot-dependencies</relativePath>
</parent>
继续查看spring-boot-dependencies底层源文件,核心代码具体如下:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-build</artifactId>
<version>${revision}</version>
<relativePath>../..</relativePath>
</parent>
在此文件的下方就可以看到
<properties>
<main.basedir>${basedir}/../..</main.basedir>
<!-- Dependency versions -->
<activemq.version>5.15.11</activemq.version>
<antlr2.version>2.7.7</antlr2.version>
<appengine-sdk.version>1.9.77</appengine-sdk.version>
<artemis.version>2.10.1</artemis.version>
<aspectj.version>1.9.5</aspectj.version>
<assertj.version>3.13.2</assertj.version>
<atomikos.version>4.0.6</atomikos.version>
<awaitility.version>4.0.2</awaitility.version>
<bitronix.version>2.1.4</bitronix.version>
<byte-buddy.version>1.10.6</byte-buddy.version>
<caffeine.version>2.8.0</caffeine.version>
<cassandra-driver.version>3.7.2</cassandra-driver.version>
<classmate.version>1.5.1</classmate.version>
<commons-codec.version>1.13</commons-codec.version>
<commons-dbcp2.version>2.7.0</commons-dbcp2.version>
<commons-lang3.version>3.9</commons-lang3.version>
<commons-pool.version>1.6</commons-pool.version>
此处省略...
</properties>
从spring-boot-dependencies底层源文件可以看出,该文件通过标签对一些常用技术框架的依赖文件 进行了统一版本号管理,例如activemq、spring、tomcat等,都有与Spring Boot 2.2.4版本相匹配的 版本,这也是pom.xml引入依赖文件不需要标注依赖文件版本号的原因。
需要说明的是,如果pom.xml引入的依赖文件不是 spring-boot-starter-parent管理的,那么在 pom.xml引入依赖文件时,需要使用标签指定依赖文件的版本号。
(2)问题2: spring-boot-starter-parent父依赖启动器的主要作用是进行版本统一管理,那么项目运 行依赖的JAR包是从何而来的?
2.spring-boot-starter-web依赖
查看spring-boot-starter-web依赖文件源码,核心代码具体如下
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.2.2.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
<version>2.2.2.RELEASE</version><scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<version>2.2.2.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<version>2.2.2.RELEASE</version>
<scope>compile</scope>
<exclusions>
<exclusion>
<artifactId>tomcat-embed-el</artifactId>
<groupId>org.apache.tomcat.embed</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.2.2.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.2.RELEASE</version>
<scope>compile</scope>
</dependency>
</dependencies>
从上述代码可以发现,spring-boot-starter-web依赖启动器的主要作用是提供Web开发场景所需的底层所有依赖
正是如此,在pom.xml中引入spring-boot-starter-web依赖启动器时,就可以实现Web场景开发,而不需要额外导入Tomcat服务器以及其他Web依赖文件等。当然,这些引入的依赖文件的版本号还是由spring-boot-starter-parent父依赖进行的统一管理。
Spring Boot除了提供有上述介绍的Web依赖启动器外,还提供了其他许多开发场景的相关依赖,我们可以打开Spring Boot官方文档,搜索“Starters”关键字查询场景依赖启动器。
列出了Spring Boot官方提供的部分场景依赖启动器,这些依赖启动器适用于不同的场景开发,使用时只需要在pox.xml文件中导入对应的依赖启动器即可。
需要说明的是,Spring Boot官方并不是针对所有场景开发的技术框架都提供了场景启动器,例如数据库操作框架MyBatis、阿里巴巴的Druid数据源等,Spring Boot官方就没有提供对应的依赖启动器。为了充分利用Spring Boot框架的优势,在Spring Boot官方没有整合这些技术框架的情况下,MyBatis、Druid等技术框架所在的开发团队主动与Spring Boot框架进行了整合,实现了各自的依赖启动器,例如mybatis-spring-boot-starter、druid-spring-boot-starter等。我们在pom.xml文件中引入这些第三方的依赖启动器时,切记要配置对应的版本号。
二、自动装配(启动流程)
问题:Spring Boot到底是如何进行自动配置的,都把哪些组件进行了自动配置?
Spring Boot应用的启动入口是@SpringBootApplication注解标注类中的main()方法,@SpringBootApplication能够扫描Spring组件并自动配置Spring Boot。
下面,查看@SpringBootApplication内部源码进行分析 ,核心代码具体如下
三、@SpringBootApplication注解的底层实现
@SpringBootApplication
public class SpringBootMytestApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootMytestApplication.class, args);
}
}
@Target(ElementType.TYPE) // 注解的适用范围,Type表示注解可以描述在类、接口、注解或枚举中
@Retention(RetentionPolicy.RUNTIME) // 表示注解的生命周期,Runtime运行时
@Documented // 表示注解可以记录在javadoc中
@Inherited // 表示可以被子类继承该注解
//--------------------------------------------------------
@SpringBootConfiguration // 标明该类为配置类
@EnableAutoConfiguration // 启动自动配置功能
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
从上述源码可以看出,@SpringBootApplication注解是一个组合注解,前面 4 个是注解的元数据信息, 我们主要看后面 3 个注解:@SpringBootConfifiguration、@EnableAutoConfifiguration、@ComponentScan三个核心注解,关于这三个核心注解的相关说明具体如下:
1.@SpringBootConfifiguration注解
@SpringBootConfifiguration注解表示Spring Boot配置类。查看@SpringBootConfifiguration注解源码,核心代码具体如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
从上述源码可以看出,@SpringBootConfifiguration注解内部有一个核心注解@Confifiguration,该注解是Spring框架提供的,表示当前类为一个配置类(XML配置文件的注解表现形式),并可以被组件扫描器扫描。由此可见,@SpringBootConfifiguration注解的作用与@Confifiguration注解相同,都是标识一个可以被组件扫描器扫描的配置类,只不过@SpringBootConfifiguration是被Spring Boot进行了重新封装命名而已。
2.@EnableAutoConfifiguration注解
@EnableAutoConfifiguration注解表示开启自动配置功能,该注解是Spring Boot框架最重要的注解,也是实现自动化配置的注解。同样,查看该注解内部查看源码信息,核心代码具体如下:
可以发现它是一个组合注解,Spring 中有很多以Enable开头的注解,其作用就是借助@Import来收集并注册特定场景相关的bean,并加载到IoC容器。@EnableAutoConfifiguration就是借助@Import来收集所有符合自动配置条件的bean定义,并加载到IoC容器。 下面,对这两个核心注解分别讲解:
(1)@AutoConfifigurationPackage注解
查看@AutoConfifigurationPackage注解内部源码信息,核心代码具体如下:
从上述源码可以看出,@AutoConfifigurationPackage注解的功能是由@Import注解实现的,它是spring框架的底层注解,它的作用就是给容器中导入某个组件类,例如@Import(AutoConfifigurationPackages.Registrar.class),它就是将Registrar这个组件类导入到容器中,可查看Registrar类中registerBeanDefifinitions方法,这个方法就是导入组件类的具体实现以及这个Registrar到底有什么作用呢?
可以看到这里有一个 Registrar对象,里面有个registerBeanDefinitions()方法,这个方法在springboot启动的过程中,是能够被调用的,然后进入到register()方法如下:
那么现在Registrar这个组件类的作用就是:把AutoConfigurationPackages(带有参数packageNames)注册到BeanDefinations中,那么以后在Springboot启动时,在整合例如JPA组件时,就可以从AutoConfigurationPackage直接获取到packageNames这个参数了,然后去扫描比如被@Entity注解的实体类等,也就是说有了这个AutoConfigurationPackages去完成一些包扫描。
(2)@Import(AutoConfigrationImportSelector.class)注解
将AutoConfifigurationImportSelector这个类导入到spring容器中, AutoConfifigurationImportSelector可以帮助springboot应用将所有符合条件@Confifiguration配置都加载到当前SpringBoot创建并使用的IoC容器(ApplicationContext)中,继续研究AutoConfifigurationImportSelector这个类,通过源码分析,这个类中是通过selectImports这个方法告诉springboot都需要导入那些组件:
那么首先先来介绍一下这个组件类AutoConfigurationImportSelector:
可以看出,此组件类实现了DeferredImportSelector类,以及很多的Aware接口,这些Aware接口来实现一些回调方法,通过这些回调,把AutoConfigurationImportSelector的属性进行赋值。
现在来看一下DeferredImportSelector这个类:
可以看到在这个DeferredImportSelector类中,有一个内部接口Group接口,这个接口里面有两个方法 process()和selectImport()。为什么要强调这两个方法,因为这两个方法在SpringBoot启动的时候会被调用。
那么下面来说明一点:
跟自动配置逻辑相关的入口方法在DeferredImportSelectorGrouping类的getImport()方法处,所以我们就从getImport()方法开始入手。
先保留这个疑问,等到剖析run()方法时就会串起来的!!!
然后下面来看一下AutoConfigurationImportSelect组件类中的方法是怎么被调用的?
我们现在来看一下 DefferedImportSelectorGrouping这个类:
调用这个类DefferedImportSelectorGrouping的getImport()方法,在这个方法里面又会去调用 group.process() 和 group.selectImports(),去找这两个方法的具体实现:
Group.process()方法
可以看出,就是AutoConfigurationImportSelector中的内部类AutoConfigurationGroup实现了Group接口,并且此内部类中也有process()和selectImport()方法,下面来看这两个方法的具体实现:
下面来看getAutoConfigurationEntry()这个方法的具体实现:返回AutoConfigurationEnty对象中封装了符合条件的自动配置类以及要排除的类:
下面来看getCandidateConfigurations()这个方法,如何获取自动配置类:从classpath下面所有的META-INF/spring.factories文件中读取EnableAutoConfiguration的类
下面来看 SpringFactoriesLoader.loadFactoryNames(要获取的类, 类加载器)
SpringFactoriesLoader会加载所有jar包下的 META-INF/spring.factories
这样的话,就会把所有spring.factories中的自动配置类的全限定路径给拿到了,现在回到getCandidateConfigurations()这个方法,然后对这个List进行筛选以及剔除,下面看比较重要的filter()**这个过滤方法:
如果要让RabbitAutoConfiguration这个自动配置类生效的话,必须在pom.xml导入rabbit相关的依赖,这样的在classpath下面才会有RabbitTemplate等相关的类,这样RabbitAutoConfiguration自动配置类才会生效。
Group.selectImports()方法
那么现在问题来了:现在能够获取到所有的自动配置类的全限定类目,但是SpringBoot是在什么时候让这些自动配置类生效的呢?
那么在SpringBoot启动的过程中,可以通过AutoConfigurationImportSelectors拿到所有的要装配的自动配置类,然后在什么时间点来让这些自动配置类完成自动装配的呢?下面就来解决这个问题!!!
(3)@ComponentScan注解
@ComponentScan注解具体扫描的包的根路径由Spring Boot项目主程序启动类所在包位置决定,对标注了@Service、@Controller、@Component等这些注解的类进行扫描,从而生成实例存在容器中,如果没有配置路径(basePackage为空的话)的话,就会对此注解@ComponentScan的类的所在包及其子包的目录进行扫描
总结:
@SpringBootApplication 的注解的功能就分析差不多了, 简单来说就是 3 个注解的组合注解:
|- @SpringBootConfiguration
|- @Configuration //通过javaConfig的方式来添加组件到IOC容器中
|- @EnableAutoConfiguration
|- @AutoConfigurationPackage //自动配置包,与@ComponentScan扫描到的添加到IOC
|- @Import(AutoConfigurationImportSelector.class) //到META-
INF/spring.factories中定义的bean添加到IOC容器中
|- @ComponentScan //包扫描
四、run()方法的底层实现
现在我们先来抛出几个问题,然后带着这几个问题来进行run()的底层源码分析:
问题1:@SpringBootApplication是如何被解析的,以及拿到被解析出来的自动配置类路径,如何让这些自动配置工厂类生效的?
问题2:@SpringBootApplication中的@ComponentScan(是用来进行包或者注解扫描的)注解是怎么被解析的?以及被标注了@Controller的类是怎么生成实例对象的?
每个Spring Boot项目都有一个主程序启动类,在主程序启动类中有一个启动项目的main()方法, 在该方法中通过执行SpringApplication.run()即可启动整个Spring Boot程序。
问题:那么SpringApplication.run()方法到底是如何做到启动Spring Boot项目的呢?
下面我们查看run()方法内部的源码,核心代码具体如下:
调用SpringApplication的run()方法,然后调用重载方法:
主要来做两件事:1.初始化SpringApplication 2.执行run方法
1. SpringApplication的初始化
下面来具体的看一下这4个方法的具体实现:
(1) 推断应用类型 为 WebApplicationType.SERVLET
(2) 设置一些初始化器
getSpringFactoriesInstances(ApplicationContextInitializer.class),这个方法一定要牢牢记住,因为后面还会使用很多次。
下面来看这个方法的具体代码实现:
那么生成的这些ApplicationContextInitializer初始化器,具体有什么作用呢,这里就是先把这些ApplicationContextInitializer初始化器准备好,等到run()方法执行的时候再进行调用。
(3) 设置一些监听器
那么这些监听器的作用:可以针对或者完成对spring容器生命周期的一个监听,可以在springboot的启动过程中生命周期的各个阶段来完成对这些监听器的调用。
(4) 根据调用栈,来推断main方法的类名
总结:
其实就是一个准备过程,一些初始化过程。这些准备工作在run()方法中都会被用到。
2. run()方法的执行
下面就是run()方法的具体实现,然后我们会进入到每个方法里面去看其具体实现:
1. 获取并启动监听器getRunListeners()
我们这里带着问题去看源码:获取并启动了具体哪个监听器?有什么作用?
下面就是获取到这个SpringApplicationRunListeners监听器代码实现:
那么为什么这个监听器没有在SpringApplication初始化过程中进行初始化?
其实可以这样理解:SpringApplication初始化过程中初始化的那些监听器可以当作是一个观察者模式的体现,而这里在SpringBoot的run启动过程中实例化的监听器是用来通知上面的那些监听器的。
然后启动这些监听器:
2. 构造应用上下文环境prepareEnvironment()
构造应用上下文环境:
根据webApplicationType确定应用容器环境Servlet:
启动相应的监听器,EventPubshiingListener通知其他的监听器,这里会通知ConfigFileApplicationListener这个监听器来加载环境中的配置文件:
通过这个ConfigFileApplicationListener监听器就可以把application.properties或者application.yml的配置信息给加载到容器中。
3. 初始化应用上下文环境createApplicationContext()
那么什么叫做上下文对象呢?
我认为:上下文对象,就是当前应用当前环境当前范围中的一个属性集合。
现在要创建这个上下文对象:
也就是这个上下文对象:
当把 AnnotationConfigServletWebServerApplicationContext进行实例化的过程中,此时ioc容器也会被创建。
此时context中的beanFactory就是 DefaultListableFactory就是ioc容器的本来面目,在这个类里面有一个beanDefinitionMap,就是来存放bean对象的。
4. 刷新应用上下文前的准备工作prepareContext()
看到准备工作:其实就是来进行一些赋值操作的,上面把应用上下文创建出来了,这里就是来赋值的。还会去创建一些bean对象存到ioc容器中。
下面来看prepareContext()的源码:
之前在new SpringApplication()中创建的初始化器的启动:
加载我们的启动类,也就是主类,并且将启动类注册到容器中:
需要获取getBeanDefinitionRegistry(context):
获取BeanDefinitionRegistry:
点击loader.load()方法,又调用了load()方法:
将核心启动类也就是主类的beanDefinition注册进beanDefinitionMap中:
是如何来实现的呢?
就找到BeanDefinitionRegistry.registerBeanDefinition() 注册beanDefinition:
当前在context上下文中存放的就是DefaultListablBeanFactory(ioc容器)。
BeanDefinitionRegistry的 registerBeanDefinition()方法其实就是向ioc容器(也即是DefaultListableBeanFactory)的beanDefinitionMap中注册beanDefinition。
然后就是看DefaultListablBeanFactory.registerBeanDefinition()方法具体做了哪些操作:
总结:prepareContext()的主要工作:
向context完成一些属性的设置
将主类生成实例对象,存到容器中。
5. 刷新应用上下文refreshContext()
现在开始看refreshContext()的源码实现:
转化成了***AbstractApplicationContext***,是不是感到很熟悉,再进行Spring源码分析的时候就是主要分析的AbstractApplicationContext的refresh()方法,其实现在我们已经走到了spring的源码中了。
其实前4步,已经完成springboot的创建,在进行refreshContext()方法执行的时候,剩下的创建对象的过程等等都交给了spring来处理。
下面来具体分析其中的部分重要方法:
invokeBeanFactoryPostProcessors()
继续走registryProcessor.postProcessBeanDefinitionRegistry()方法
调用 ConfigurationClassPostProcessor.processConfigBeanDefinitions():
在此方法里面new了一个ConfigurationClassParser配置类解析器
然后调用 ConfigurationClassParser.parse(candidates)方法,其实这个candidates就是启动类
下面就是进入 **ConfigurationClassParser.parse()**方法,这个方法很重要
首先获取到启动类的BeanDefinition,然后判断启动类是否是注解类,然后进行解析,
然后下面就是真正来解析 @ComponentScan和@Import等注解的
下面咱们一个一个来,先看parse()方法,看看到底是如何解析
@ComponentScan注解和@Import注解的!!!
下面的重点来了,就是为什么@ComponentScan能够获取到对应的扫描路径,从而把 标注了@Controller、@Service等标注的对象 生成实例对象存到ioc容器中。
然后现在ComponentScan就可以扫描到包和子包路径下面的各种被标注的类,例如@Controller、@Service、@Component,那么这些对象又是怎么生成实例存到ioc容器当中的呢?
调用scanner.doScan()方法:
这样的话,就可以拿到启动类目录以及子目录下面的所有被标注了@Component、@Controller、@Service等标注的BeanDefinition集合.
到此,@ComponentScan的解析过程到此结束了!
下面的就是@Import注解的解析过程:其实和@ComponentScan都差不多>
递归调用@SpringBootApplication中的所有的@Import注解,从而让@Import中导入的组件类生效:
总结:
那么到此,基本上@SpringBootApplication此注解的解析工作基本完成,就是@SpringBootApplication注解内部底层注解的解析完成!
那么现在让我们回一下,再回到最初的parse()方法,通过parse()方法拿到注解组件类,然后回到第二个重要方法deferredImportSelectorHandler.process()方法
调用process()方法:
调用processGroupImports()方法,下面就是见证奇迹的时刻了!!!
然后就会走到getImports()方法中,
执行里面的process()方法和selectImports()方法
然后就会调用前面说的AutoConfigurationImportSelector.process()和selectorImports()方法
因为在上面的parse()方法中,因为@Import(AutoConfigurationImportSelector.class)已经把AutoConfigurationImportSelector这个bean注册到ioc容器了,可以使用了。
经过了这两步之后,就可以获取到所有jar包下META-INF/spring.factories中的所有需要EnableAutoConfiguration的自动配置类。
那么到此ConfigurationClassParser.parse()才执行完!!!是不是就结束了呢???
还没有,此时,只是获取到了spring.factories所有需要自动配置的配置类的全限定路径,这些自动配置类还没有真正生效,还没有注册到DefaultListableBeanFactory(ioc容器)的beanDefinitionMap中, 咱们继续走!
到此,是不是终于松了一口气!!! 咱们的第5步终于分析完了 refreshContext()!!
下面来总结一下:
主要工作是:完成了@SpringBootApplication以及其下面的@Import和@ComponentScan等注解的解析工作,以及把@SpringBootApplication所在包及其子包下面的被@Service@Controller@Component等注解标注的类的注册工作,还有把jar包下面的所有的META-INF/spring.factories中的所有符合条件(@ConditionOnClass、@ConditionOnBean等等)的自动配置类的注册工作!!!
6. 刷新应用上下文后的扩展接口afterRresh()
点击进入,发现:这是一个空实现的方法
其实就是提供的一个扩展接口,有一些自定义功能的时候就可以去实现这个接口。
到此run()方法介绍完毕,下面来做一个总结:总结一些重要的工作:
在第3步 createApplicationContext(),会创建上下文对象和ioc容器对象,
在第4步 prepareContext(),会完成主类(启动类)对象的创建并添加到ioc容器中,
在第5步 refreshContext(),会去解析@Import注解、@ComponentScan注解,通过扫描路径得到被@Controller、@Service、@Component所标注的这些类并且把这些对象生成实例对象存到ioc容器中,同时也会让spring.factories中的符合条件的自动配置类进行生效,完成自动装配。
下面来进行一些拓展点的讲解,借着这个热乎劲,咱们继续!!!哈哈哈,希望你还保持清醒。
主要包括:
springboot嵌入式tomcat源码剖析
Springboot中springmvc自动配置源码剖析
五、SpringBoot嵌入式tomcat源码剖析
其实关于springboot嵌入式tomcat源码剖析挺简单的,那么下面我们走起:
下面我们继续来看第5步:refreshContext()方法
继续进入
还是走到了spring的源码,那么继续:
进入onRefresh():找到其具体类的实现方法 ServeltWebServerApplicationContext:
下面继续走,看是如何创建Servlet容器的:先创建了Servlet容器工厂对象
下面getWebServer()这个方法:
最终找到TomcatServletWebServerFactory.getWebServer()方法:
来进行Tomcat实例对象的创建,和一些配置信息,那么这个Tomcat是如何启动的呢?
Tomcat的启动:
进入new TomcatWebServer()的构造方法:
下面就是initialize()初始化方法:
总结:
所以,在SpringBoot的启动过程中,就会在run()方法的refreshContext()方法中,完成内嵌tomcat的创建和启动。
六、SpringBoot中springmvc自动配置源码剖析
回忆一下,在自定义的一个SpringBoot项目中,在项目中就可以直接使用@Controller和@RequestMapping等注解,我们也知道,是因为我们在创建SpringBoot项目的时候在pom.xml中以来了spring-boot-starter-web等依赖启动器,会引入spring-mvc相关的依赖jar包,所以就可以用到这些注解,来完成springmvc的相关设置。但是不要忘了,我们之前在使用springmvc的时候,可是在web.xml中配置了DispatcherServlet中央控制器的,但是在SpringBoot项目中,你有见到DispatcherServlet了吗?况且连web.xml都没有。
最重要的一点是:此servlet还需要添加到tomcat中的servletContext中,才能够真正的对外提供请求处理服务。
什么意思呢?就是Springboot在启动过程中,完成了DispatcherServlet对象的创建并且放到ioc容器中,但是这还不够,还要把DispatcherServlet添加到tomcat的servletContext中,才能真正的对外提供请求。
在这里多说一句:Servlet3.0的规范,要添加一个servlet,除了采用xml配置的方式,还有一种通过代码的方式,如下:
servletContext.addServlet(name, this.servlet)
我们就来进行springboot的源码剖析,看看是如何来完成springmvc的自动装配的:
(1) Springboot中关于springmvc的自动配置都具体做了什么?
(2) DispatcherServlet是怎么添加到servletContext中的
继续:
那么很简单,在spring.factories中肯定会有这个DispatcherServlet的相关配置类
就是如下的DispatcherServletAutoConfiguratoin类,下面就来看这个类:
那么很简单,下面我们就来重点看这个类:DispatcherServletAutoConfiguration
可以看出,在这个类里面,主要有两个内部类DispatcherServletConfiguration和DispatcherServletRegistrationConfiguration。
下面来看一下这两个内部类
可以看出,这个自动配置类DispatcherServletAutoConfiguration
主要完成了两个Bean的注册:
DispatcherServlet:前端控制器
DispatcherServletRegistrationBean:DispatcherServlet的注册类,负责将该servlet注册到servletContext的类
那么这就是 Springboot中关于springmvc的自动配置都具体做了什么? 这个问题的答案,主要是做了这两件事,进行两个Bean的注册
DispatcherServletRegistrationBean的类图
既然该类的职责是负责注册DispatcherServlet,那么我们得知道什么是否触发注册操作。为此,我们看看DispatcherServletRegistrationBean的类图:
下面我们就来看看注册DispatcherServlet的流程:
ServletContextInitializer
我们看到,最上面是一个ServletContextInitializer接口。我们可以知道,实现该接口意味着是用来初始化ServletContext的。我们看看该接口,只有一个方法,那么实现该接口意味着用来初始化ServletContext。
找到其具体实现,根据类图找到RegistrationBean
然后调用其内部的register()方法,
这是一个抽象方法,
DynamicRegistrationBean
再来看DynamicRegistrationBean是如何实现register()方法的:
进入addRegistration()方法
又是一个抽象方法
ServletRegistrationBean
看看ServletRegistrationBean是怎么实现addRegistrationBean方法的
那么有个疑问?
到底springboot启动的时候,是怎么调用到这个 ServletRegistrationBean.addRegistration()这个方法的呢???
那么还记得不记得我们之前在说 Springboot之如何配置内嵌tomcat的时候,我让大家记得的一个方法调用getSelfInitializer(),现在来看一下
那么我们下面来看一下getSelfInitializer()这个方法具体做了什么事情?
我们就可以看到就走到了我们上面所说的onStartup()这个方法,然后就会进入到RegistrationBean的register()这个方法
最终还是会走到ServletRegistrationBean的addRegistration()这个方法
那么到此:SpringBoot中springmvc的自动配置源码就说完了!!!
不知道大家会有如何感想呢,其实在自己真正整理完这个笔记之后呢,还是回味了很久的,感觉太难了,也太绕了,整整花费了小两天的时间,但是最终还是有收获的,也最终整理完了,所以就想拿出来和大家进行分享分享,并且自己也是一个工作了才几年的小菜鸟,可能有的地方说的不对,也请各位大佬多多批评指点,一起学习,共同进步!!!
文章内容输出来源:拉勾教育Java高薪训练营