SpringBoot原理深入及源码剖析

1 依赖管理
问题:(1)为什么导入dependency时不需要指定版本?
在Spring Boot入门程序中,项目pom.xml文件有两个核心依赖,分别是spring-boot-starter-parent和spring-boot-starter-web,关于这两个依赖的相关介绍具体如下:
1.spring-boot-starter-parent依赖
在pom.xml文件中找到spring-boot-starter-parent依赖,示例代码如下:

<!-- Spring Boot父项目依赖管理 -->
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent<11./artifactId>
    <version>2.2.2.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>
上述代码中,将spring-boot-starter-parent依赖作为Spring Boot项目的统一父项目依赖管理,并将项目版本号统一为2.2.2.RELEASE,该版本号根据实际开发需求是可以修改的
进入并查看spring-boot-starter-parent底层源文件,发现spring-boot-starter-parent的底层有一个父依赖spring-boot-dependencies,核心代码具体如下
<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-dependencies</artifactId>
  <version>2.2.2.RELEASE</version>
  <relativePath>../../spring-boot-dependencies</relativePath>
</parent>
继续查看spring-boot-dependencies底层源文件,核心代码具体如下:
<properties>
  <activemq.version>5.15.11</activemq.version>
  ...
  <solr.version>8.2.0</solr.version>
  <mysql.version>8.0.18</mysql.version>
  <kafka.version>2.3.1</kafka.version><spring-amqp.version>2.2.2.RELEASE</spring-amqp.version>
  <spring-restdocs.version>2.0.4.RELEASE</spring-restdocs.version>
  <spring-retry.version>1.2.4.RELEASE</spring-retry.version>
  <spring-security.version>5.2.1.RELEASE</spring-security.version>
  <spring-session-bom.version>Corn-RELEASE</spring-session-bom.version>
  <spring-ws.version>3.0.8.RELEASE</spring-ws.version>
  <sqlite-jdbc.version>3.28.0</sqlite-jdbc.version>
  <sun-mail.version>${jakarta-mail.version}</sun-mail.version>
  <tomcat.version>9.0.29</tomcat.version>
  <thymeleaf.version>3.0.11.RELEASE</thymeleaf.version>
  <thymeleaf-extras-data-attribute.version>2.0.1</thymeleaf-extras-data-attribute.version>
  ...
</properties>
从spring-boot-dependencies底层源文件可以看出,该文件通过标签对一些常用技术框架的依赖文件进行了统一版本号管理,例如activemq、spring、tomcat等,都有与Spring Boot 2.2.2版本相匹配的版本,这也是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父依赖进行的统一管理。
 
2 自动配置
概念:能够在我们添加jar包依赖的时候,自动为我们配置一些组件的相关配置,我们无需配置或者只需要少量配置就能运行编写的项目
问题:Spring Boot到底是如何进行自动配置的,都把哪些组件进行了自动配置?
Spring Boot应用的启动入口是@SpringBootApplication注解标注类中的main()方法,@SpringBootApplication : SpringBoot 应用标注在某个类上说明这个类是 SpringBoot 的主配置类, SpringBoot 就应该运行这个类的 main() 方法启动 SpringBoot 应用。
下面,查看@SpringBootApplication内部源码进行分析 ,核心代码具体如下:
@SpringBootApplication
public class SpringbootDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootDemoApplication.class, args);
}
}
进入到 @SpringBootApplication 内,观察其做了哪些工作:
@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 {
// 根据class来排除特定的类,使其不能加入spring容器,传入参数value类型是class类型。
@AliasFor(annotation = EnableAutoConfiguration.class)
Class<?>[] exclude() default {};
// 根据classname 来排除特定的类,使其不能加入spring容器,传入参数value类型是class的全类名字符串数组。
@AliasFor(annotation = EnableAutoConfiguration.class)
String[] excludeName() default {};
// 指定扫描包,参数是包名的字符串数组。
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
// 扫描特定的包,参数类似是Class类型数组。
@AliasFor(annotation = ComponentScan.class, attribute ="basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};
}
从上述源码可以看出,@SpringBootApplication注解是一个组合注解,前面 4 个是注解的元数据信息, 我们主要看后面 3 个注解:@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan三个核心注解,关于这三个核心注解的相关说明具体如下:
1.@SpringBootConfiguration注解
@SpringBootConfiguration : SpringBoot 的配置类,标注在某个类上,表示这是一个 SpringBoot的配置类。
查看@SpringBootConfiguration注解源码,核心代码具体如下。
 
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
// 配置类的作用等同于配置文件,配置类也是容器中的一个对象 public @interface SpringBootConfiguration { }
从上述源码可以看出,@SpringBootConfiguration注解内部有一个核心注解@Configuration,该注解是Spring框架提供的,表示当前类为一个配置类(XML配置文件的注解表现形式),并可以被组件扫描器扫描。由此可见,@SpringBootConfiguration注解的作用与@Configuration注解相同,都是标识一个可以被组件扫描器扫描的配置类,只不过@SpringBootConfiguration是被Spring Boot进行了重新封装命名而已.
2.@EnableAutoConfiguration注解
@EnableAutoConfiguration :开启自动配置功能,以前由我们需要配置的东西,现在由 SpringBoot帮我们自动配置,这个注解就是 Springboot 能实现自动配置的关键。同样,查看该注解内部查看源码信息,核心代码具体如下
 
 1 // 自动配置包
 2 @AutoConfigurationPackage
 3 // Spring的底层注解@Import,给容器中导入一个组件;
 4 // 导入的组件是AutoConfigurationPackages.Registrar.class
 5 @Import(AutoConfigurationImportSelector.class)
 6 // 告诉SpringBoot开启自动配置功能,这样自动配置才能生效。
 7 public @interface EnableAutoConfiguration {
 8 String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
 9 // 返回不会被导入到 Spring 容器中的类
10 Class<?>[] exclude() default {};
11 // 返回不会被导入到 Spring 容器中的类名
12 String[] excludeName() default {};
13 }
View Code
可以发现它是一个组合注解, Spring 中有很多以 Enable 开头的注解,其作用就是借助 @Import
来收集并注册特定场景相关的 Bean ,并加载到 IOC 容器。@EnableAutoConfiguration就是借助
@Import来收集所有符合自动配置条件的bean定义,并加载到IoC容器。
下面,对这两个核心注解分别讲解 :
(1)@AutoConfigurationPackage注解
查看@AutoConfigurationPackage注解内部源码信息,核心代码具体如下:
 
1 @Target({ElementType.TYPE})
2 @Retention(RetentionPolicy.RUNTIME)
3 @Documented
4 @Inherited
5 @Import({Registrar.class}) // 导入Registrar中注册的组件
6 public @interface AutoConfigurationPackage {
7 }
View Code
从上述源码可以看出,@AutoConfigurationPackage注解的功能是由@Import注解实现的,它是spring框架的底层注解,它的作用就是给容器中导入某个组件类,例如@Import(AutoConfigurationPackages.Registrar.class),它就是将Registrar这个组件类导入到容器中,可查看Registrar类中registerBeanDefinitions方法,这个方法就是导入组件类的具体实现 :
从上述源码可以看出,在Registrar类中有一个registerBeanDefinitions()方法,使用Debug模式启动项目,可以看到选中的部分就是com.lagou。也就是说,@AutoConfigurationPackage注解的主要作用就是将主程序类所在包及所有子包下的组件到扫描到spring容器中。
因此 在定义项目包结构时,要求定义的包结构非常规范,项目主程序启动类要定义在最外层的根目录位置,然后在根目录位置内部建立子包和类进行业务开发,这样才能够保证定义的类能够被组件扫描器扫描
(2)@Import({AutoConfigurationImportSelector.class})注解
将 AutoConfigurationImportSelector 这个类导入到 Spring 容器中,AutoConfigurationImportSelector 可以帮助 Springboot 应用将所有符合条件的 @Configuration配置都加载到当前 SpringBoot 创建并使用的 IOC 容器( ApplicationContext )中。
继续研究AutoConfigurationImportSelector这个类,通过源码分析这个类中是通过selectImports这个方法告诉springboot都需要导入那些组件: 
深入研究loadMetadata方法
AutoConfigurationImportSelector类 getAutoConfigurationEntry方法
 1 protected AutoConfigurationEntry
 2 getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
 3 AnnotationMetadata annotationMetadata) {
 4 //判断EnabledAutoConfiguration注解有没有开启,默认开启
 5 if (!isEnabled(annotationMetadata)) {
 6 return EMPTY_ENTRY;
 7 }
 8 //获得注解的属性信息
 9 AnnotationAttributes attributes = getAttributes(annotationMetadata);
10 //获取默认支持的自动配置类列表
11 List<String> configurations =
12 getCandidateConfigurations(annotationMetadata, attributes);
13 //去重
14 configurations = removeDuplicates(configurations);
15 //去除一些多余的配置类,根据EnabledAutoConfiguratio的exclusions属性进行排除
16 Set<String> exclusions = getExclusions(annotationMetadata, attributes);
17 checkExcludedClasses(configurations, exclusions);
18 configurations.removeAll(exclusions);
19 //根据pom文件中加入的依赖文件筛选中最终符合当前项目运行环境对应的自动配置类
20 configurations = filter(configurations, autoConfigurationMetadata);
21 //触发自动配置导入监听事件
22 fireAutoConfigurationImportEvents(configurations, exclusions);
23 return new AutoConfigurationEntry(configurations, exclusions);
24 }
View Code
深入getCandidateConfigurations方法
这个方法中有一个重要方法loadFactoryNames,这个方法是让SpringFactoryLoader去加载一些组件的名字。

继续点开loadFactory方法 
 1 public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable
 2 ClassLoader classLoader) {
 3 //获取出入的键
 4 String factoryClassName = factoryClass.getName();
 5 return
 6 (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName,
 7 Collections.emptyList());
 8 }
 9 private static Map<String, List<String>> loadSpringFactories(@Nullable
10 ClassLoader classLoader) {
11 MultiValueMap<String, String> result =
12 (MultiValueMap)cache.get(classLoader);
13 if (result != null) {
14 return result;
15 } else {
16 try {
17 //如果类加载器不为null,则加载类路径下spring.factories文件,将其中设置的
18 配置类的全路径信息封装 为Enumeration类对象
19 Enumeration<URL> urls = classLoader != null ?
20 classLoader.getResources("META-INF/spring.factories") :
21 ClassLoader.getSystemResources("META-INF/spring.factories");
22 LinkedMultiValueMap result = new LinkedMultiValueMap();
23 //循环Enumeration类对象,根据相应的节点信息生成Properties对象,通过传入的
24 键获取值,在将值切割为一个个小的字符串转化为Array,方法result集合中
25 while(urls.hasMoreElements()) {
26 URL url = (URL)urls.nextElement();
27 UrlResource resource = new UrlResource(url);
28 Properties properties =
29 PropertiesLoaderUtils.loadProperties(resource);
30 Iterator var6 = properties.entrySet().iterator();
31 while(var6.hasNext()) {
32 Entry<?, ?> entry = (Entry)var6.next();
33 String factoryClassName =
34 ((String)entry.getKey()).trim();
35 String[] var9 =
36 StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
37 int var10 = var9.length;
38 for(int var11 = 0; var11 < var10; ++var11) {
39 String factoryName = var9[var11];
40 result.add(factoryClassName, factoryName.trim());
41 }
42 }
43 }
44 cache.put(classLoader, result);
45 return result;
View Code
会去读取一个 spring.factories 的文件,读取不到会表这个错误,我们继续根据会看到,最终路径的长这样,而这个是spring提供的一个工具类 
1 public final class SpringFactoriesLoader {
2 public static final String FACTORIES_RESOURCE_LOCATION = "METAINF/spring.factories";
3 }
View Code
它其实是去加载一个外部的文件,而这文件是在 
@EnableAutoConfiguration就是从classpath中搜寻META-INF/spring.factories配置文件,并将其org.springframework.boot.autoconfigure.EnableutoConfiguration对应的配置项通过反射(JavaRefletion)实例化为对应的标注了@Configuration的JavaConfig形式的配置类,并加载到IOC容器中
总结
因此springboot底层实现自动配置的步骤是:
1. springboot应用启动;
2. @SpringBootApplication起作用;
3. @EnableAutoConfiguration;
4. @AutoConfigurationPackage:这个组合注解主要是
@Import(AutoConfigurationPackages.Registrar.class),它通过将Registrar类导入到容器中,而Registrar类作用是扫描主配置类同级目录以及子包,并将相应的组件导入到springboot创建管理的容器中;
5. @Import(AutoConfigurationImportSelector.class):它通过将AutoConfigurationImportSelector类导入到容器中,AutoConfigurationImportSelector类作用是通过selectImports方法执行的过程中,会使用内部工具类SpringFactoriesLoader,查找classpath上所有jar包中的META-INF/spring.factories进行加载,实现将配置类信息交给SpringFactory加载器进行一系列的容器创建过程 
3. @ComponentScan注解
@ComponentScan注解具体扫描的包的根路径由Spring Boot项目主程序启动类所在包位置决定,
在扫描过程中由前面介绍的@AutoConfigurationPackage注解进行解析,从而得到Spring Boot项目主
程序启动类所在包的具体位置
总结:
@SpringBootApplication 的注解的功能, 简单来说就是 3 个注解的组合注解: 
1 |- @SpringBootConfiguration
2 |- @Configuration //通过javaConfig的方式来添加组件到IOC容器中
3 |- @EnableAutoConfiguration
4 |- @AutoConfigurationPackage //自动配置包,与@ComponentScan扫描到的添加到IOC
5 |- @Import(AutoConfigurationImportSelector.class) //到METAINF/spring.factories中定义的bean添加到IOC容器中
6 |- @ComponentScan //包扫描
View Code

 

3.执行原理

每个Spring Boot项目都有-个主程序启动类,在主程序启动类中有-个启动项目的main()方法,在该 方法中通过执行SpringApplication.run()即可启动
整个Spring Boot程序。
问题:那么SpringApplication.run()方法到底是如何做到启动Spring Boot项目的呢?

SpringApplication.run()方法内部执行了两个操作,分别是SpringApplication实 例的初始化创建和调用run()启动项目,这两个阶段的实现具体说明如下
1 . SpringApplication实例的初始化创建
查看SpringApplication实例对象初始化创建的源码信息,核心代码具体如下
从上述源码可以看出, SpringApplication的初始化过程主要包括4部分,具体说明如下。
(1) this.webApplicationType = WebApplicationType.deduceFromClasspath()
用于判断当前webApplicationType应用的类型。 deduceFromClasspath()方法用于查看Classpath类路 径下是否存在某个特征类,从而判断当前webApplicationType类型是SERVLET应用 (Spring 5之前的传 统MVC应用)还是REACTIVE应用 (Spring 5开始出现的WebFlux交互式应用)
(2) this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class))
用于SpringApplication应用的初始化器设置。在初始化器设置过程中,会使用Spring类加载器 SpringFactoriesLoader从META-INF/spring.factories类路径下的META-INF下的spring.factores文件中 获取所有可用的应用初始化器类ApplicationContextInitializer。
(3) this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class))
用于SpringApplication应用的监听器设置。监听器设置的过程与上-步初始化器设置的过程基本-样,
也是使用SpringFactoriesLoader从META-INF/spring.factories类路径下的META-INF下的 spring.factores文件中获取所有可用的监听器类ApplicationListener。
(4) this.mainApplicationClass = this.deduceMainApplicationClass()
用于推断并设置项目main()方法启动的主程序启动类

2 .  项目的初始化启动

第-步:获取并启动监听器
第二步:根据SpringApplicationRunListeners以及参数来准备环境
第三步:创建Spring容器
第四步: Spring容器前置处理
第五步:刷新容器
第六步: Spring容器后置处理
第七步:发出结束执行的事件
第八步:执行Runners

 

 

 

posted @ 2022-11-25 16:36  muzinan110  阅读(313)  评论(0编辑  收藏  举报