最近在学习Springboot,不少文章和视频在完成了第一个快速入门的项目之后就直接进入了Springboot运行的原理部分,因此决定写一篇文章加深理解。
原理的理解主要使用了查看源码和画流程图的方式。
pom.xml
对于一个maven项目,我们一般首要分析其pom文件,查看相关依赖。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
可以看到依赖方面都是很简单的内容,加入了测试启动器和web启动器以及springboot的build插件,但我们发现了一个不一般的地方,他们都没有版本号!这是为什么呢?
我们往上查看,可以发现这样的父依赖。
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
我们点进去查看,会发现。。。还有一层父依赖!
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.3.4.RELEASE</version>
</parent>
继续点进去查看。
<properties>
<activemq.version>5.15.13</activemq.version>
<antlr2.version>2.7.7</antlr2.version>
<appengine-sdk.version>1.9.82</appengine-sdk.version>
<artemis.version>2.12.0</artemis.version>
<aspectj.version>1.9.6</aspectj.version>
<assertj.version>3.16.1</assertj.version>
<atomikos.version>4.0.6</atomikos.version>
<awaitility.version>4.0.3</awaitility.version>
<bitronix.version>2.1.4</bitronix.version>
<build-helper-maven-plugin.version>3.1.0</build-helper-maven-plugin.version>
<byte-buddy.version>1.10.14</byte-buddy.version>
<caffeine.version>2.8.5</caffeine.version>
<cassandra-driver.version>4.6.1</cassandra-driver.version>
...........
```
这样的信息就是这些依赖尽头的主体了,这个文件有大量的配置信息,标注了每个可能用到的依赖的版本号,因此我们不用指定依赖的版本,springboot会根据你使用的版本自动给你安排合适的版本,再也不用担心依赖版本的冲突导致的崩溃啦!
pom.xml文件我们就说到这里,接下来才是重头戏了!
思维导图:
启动类的@SpringBootApplication注解
启动类包括两个重要的部分,一个是@SpringBootApplication注解,另一个是其中的run方法,我们先从这个注解开始说起。
首先,这个注解的作用,猜也猜得到,是标注这个应用是一个springboot应用,这样springboot就可以帮我们对其进行自动配置,我们想要了解,spring boot是如何通过注解来自动装配的,装配了什么。
老办法,查看源码,我点!
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
以上是@SpringBootApplication注解之下的几个注解,出去那些基础的之外,我们可以看见三个特别显眼的,@SpringBootConfiguration,@ComponentScan,@EnableAutoConfiguration,我们逐个查看他们的源码。
@SpringBootConfiguration
他的源码:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
乏善可陈。。。根据其下的@Configuration可以猜到,它是起到一个标注作用的注解,标志当前应用为一个springboot应用。
我们回到上一层查看其他注解的源码。
@ComponentScan
这个更没啥好说的了,源码都没必要看了,他的作用是扫描指定包下的组件,将它们加载到Spring的IOC容器之中,很重要,但不是我们探究的重点。
我们查看最后一个注解的源码。
@EnableAutoConfiguration
看得出来,他的作用是启用自动配置,我们重点关注如何启用,怎么启用,启用了什么。
他的源码:
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
看起来重要的注解就这两个,第一个看翻译是自动配置包,我们点进去查看他的源码:
@Import({Registrar.class})
噢,这是一个注册器,根据相关源码猜测是将bean注册导入到容器之中(若有错误希望指正)。
我们回头,看看import导入的东西。
自动配置导入选择器,我们猜测它是导入了需要的配置文件,查看源码:
我这里截图还没有截全。。。我们发现其中有相当多的方法,我们的目的是查看如何加载组件的,因此寻找configuration相关的方法。
找到了!getCandidateConfigurations,获取候选配置,这个方法中又使用了SpringFactoriesLoader,我们继续深入。
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
看到了看起来是文件的东西!我们使用全局搜索,发现它在springboot的jar包内,我们再点开来看看
我们根据源头打开spring.factories , 看到了很多自动配置的文件;这就是自动配置根源所在!可以随便点开看看,都可以看到这些一个个的都是JavaConfig配置类,而且都注入了一些Bean。
至此,我们大概明白了Spring boot的自动装配原理了:
自动配置真正实现是从classpath中搜寻所有的META-INF/spring.factories配置文件 ,并将其中对应的 org.springframework.boot.autoconfigure. 包下的配置项,通过反射实例化为对应标注了 @Configuration的JavaConfig形式的IOC容器配置类 , 然后将这些都汇总成为一个实例并加载到IOC容器中。
所以其实刚刚那一大串套娃注解其实都是为了拿到spring.factories。。。我不太懂设计模式,不太懂这样做的意义是什么样的哈哈哈。
结论
-
SpringBoot在启动的时候从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的值
-
将这些值作为自动配置类导入容器 , 自动配置类就生效 , 帮我们进行自动配置工作;
-
整个J2EE的整体解决方案和自动配置都在springboot-autoconfigure的jar包中;
-
它会给容器中导入非常多的自动配置类 (xxxAutoConfiguration), 就是给容器中导入这个场景需要的所有组件 , 并配置好这些组件 ;
-
有了自动配置类 , 免去了我们手动编写配置注入功能组件等的工作;
思维导图:
run方法
分析该方法主要分两部分,一部分是SpringApplication的实例化,二是run方法的执行;
SpringApplication
这个类主要做了以下四件事情:
1、推断应用的类型是普通的项目还是Web项目
2、查找并加载所有可用初始化器 , 设置到initializers属性中
3、找出所有的应用程序监听器,设置到listeners属性中
4、推断并设置main方法的定义类,找到运行的主类
源码分析无力了。。。给大家一张图吧,说的很清楚了,大家可以跟着这张图自己读读源码。