2020-11-20 | Java | Unlock

SpringBoot源码分析(6)-SpringBoot内置的Starter构建原理

上一篇分析了SpringBoot外部配置属性值是如何被绑定到XxxProperties类属性上的相关源码,重要步骤总结:

  1. 首先是@EnableConfigurationProperties注解importEnableConfigurationPropertiesImportSelector后置处理器;
  2. EnableConfigurationPropertiesImportSelector后置处理器又向Spring容器中注册了ConfigurationPropertiesBeanRegistrarConfigurationPropertiesBindingPostProcessorRegistrar这两个bean
  3. 其中ConfigurationPropertiesBeanRegistrarSpring容器中注册了XxxProperties类型的beanConfigurationPropertiesBindingPostProcessorRegistrarSpring容器中注册了ConfigurationBeanFactoryMetadataConfigurationPropertiesBindingPostProcessor两个后置处理器;
  4. ConfigurationBeanFactoryMetadata后置处理器在初始化bean factory时将@Bean注解的元数据存储起来,以便在后续的外部配置属性绑定的相关逻辑中使用;
  5. ConfigurationPropertiesBindingPostProcessor后置处理器将外部配置属性值绑定到XxxProperties类属性的逻辑委托给ConfigurationPropertiesBinder对象,然后ConfigurationPropertiesBinder对象又最终将属性绑定的逻辑委托给Binder对象来完成。

可见,重要的是上面的第5步

1.思考

SpringBoot内置了各种Starter起步依赖,使用起来非常方便,大大减轻了我们的开发工作。有了Starter起步依赖,我们不用去考虑这个项目需要什么库,这个库的groupIdartifactId是什么?更不用担心引入这个版本的库后会不会跟其他依赖有没有冲突。

例如:现在开发一个web项目,只要引入spring-boot-starter-web这个起步依赖就可以了,不用考虑要引入哪些版本的哪些依赖了。以前还要考虑引入哪些依赖库,比如要引入spring-webspring-webmvc依赖等;此外,还要考虑引入这些库的哪些版本才不会跟其他库冲突等问题。

由于起步依赖跟自动配置的关系是如影随形的关系,因此本篇先站在maven项目构建的角度来宏观分析下我们平时使用的SpringBoot内置的各种Starter是怎样构建的?

2.Maven传递依赖的optional标签

在分析SpringBoot内置的各种Starter构建原理前,先来认识下Maven的optional标签,因为这个标签起到至关重要的作用。
Maven的optional标签表示可选依赖即不可传递的意思,下面直接举个例子来说明。

比如有A,BC三个库,C依赖BB依赖A。下面看下这三个库的pom.xml文件:

A的pom.xml

1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

<groupId>com.ymbj</groupId>
<artifactId>A</artifactId>
<version>1.0-SNAPSHOT</version>

</project>

B的pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

<groupId>com.ymbj</groupId>
<artifactId>B</artifactId>
<version>1.0-SNAPSHOT</version>

<!--注意是可选依赖-->
<dependencies>
<dependency>
<groupId>com.ymbj</groupId>
<artifactId>A</artifactId>
<version>1.0-SNAPSHOT</version>
<optional>true</optional>
</dependency>
</dependencies>

</project>

C的pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

<groupId>com.ymbj</groupId>
<artifactId>C</artifactId>
<version>1.0-SNAPSHOT</version>

<dependencies>
<dependency>
<groupId>com.ymbj</groupId>
<artifactId>B</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>

</project>

上面三个A,BC库的pom.xml可知,B库依赖A库,然后C库又依赖了B库,那么请想一下,Maven打包构建C库后,A库有没有被引进来?

答案肯定是没有,因为B库引入A库依赖时使用了<optional>true</optional>,即将Maven的optional标签值设为了true,此时C库再引入B库依赖时,A库是不会被引入到C库的。

同时跟Maven传递依赖有关的还有一个exclusions标签,这个表示将某个库的某个子依赖排除掉。

3.SpringBoot内置的各种Starter是怎样构建的

先来看一下SpringBoot源码内部模块图:

sb22.png

SpringBoot的Starter的构建的原理实质就是自动配置,因上图可以看到SpringBoot源码项目内部跟Starter及其自动配置有关的模块有四个:spring-boot-starters,spring-boot-actuator-autoconfigure,spring-boot-autoconfigurespring-boot-test-autoconfigure

那么,spring-boot-starters模块跟后面三个自动配置有关的模块xxx-autoconfigure模块的关系是怎样的呢?

此时我们先来看看spring-boot-starters模块里面的结构是怎样的?

sb23.png

sb24.png

可以看到spring-boot-starters模块包含了SpringBoot内置的各种starterspring-boot-starter-xxx。由于SpringBoot内置的各种starter太多,以常用的spring-boot-starter-web起步依赖来分析。

首先看下spring-boot-starter-web模块内部结构:

sb25.png

可以看到spring-boot-starter-web模块里面只有.flattened-pom.xmlpom.xml文件,而没有任何代码

若要用到SpringBoot的web功能时引入spring-boot-starter-web起步依赖即可,而现在spring-boot-starter-web模块里面没有一行代码,那么spring-boot-starter-web究竟是如何构建的呢?会不会跟spring-boot-autoconfigure自动配置模块有关?

此时就需要看下spring-boot-starter-web模块的pom.xml文件内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starters</artifactId>
<version>${revision}</version>
</parent>
<artifactId>spring-boot-starter-web</artifactId>
<name>Spring Boot Web Starter</name>
<description>Starter for building web, including RESTful, applications using Spring
MVC. Uses Tomcat as the default embedded container</description>
<properties>
<main.basedir>${basedir}/../../..</main.basedir>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
</dependencies>
</project>

可以看到,spring-boot-starter-web模块依赖了spring-boot-starter,spring-boot-starter-tomcat,spring-webspring-webmvc等模块,居然没有依赖spring-boot-autoconfigure自动配置模块!

由于spring-boot-starter-web模块肯定跟spring-boot-autoconfigure自动配置模块有关,所以spring-boot-starter-web模块肯定是间接依赖了spring-boot-autoconfigure自动配置模块。

spring-boot-starter模块是大部分spring-boot-starter-xxx模块依赖的基础模块,是核心的Starter,包括了自动配置,日志和YAML支持。关注下spring-boot-starterpom.xml文件,看看是否依赖了spring-boot-autoconfigure自动配置模块。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starters</artifactId>
<version>${revision}</version>
</parent>
<artifactId>spring-boot-starter</artifactId>
<name>Spring Boot Starter</name>
<description>Core starter, including auto-configuration support, logging and YAML</description>
<properties>
<main.basedir>${basedir}/../../..</main.basedir>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
</project>

可以看到,正是spring-boot-starter模块依赖了spring-boot-autoconfigure自动配置模块!因此,到了这里就可以得出结论了:spring-boot-starter-web模块没有一行代码,但是其通过spring-boot-starter模块间接依赖了spring-boot-autoconfigure自动配置模块,从而实现了其起步依赖的功能。

再来看下spring-boot-autoconfigure自动配置模块的内部包结构:

sb26.png

sb27.png

可以知道spring-boot-starter-web起步依赖的自动配置功能原来是由spring-boot-autoconfigure模块的web包下的类实现的。

到了这里spring-boot-starter-web起步依赖的构建基本原理就搞清楚了,但是还有一个特别重要的关键点我们还没分析。这个关键点跟Maven的optional标签有的作用有关。

思考一个问题:平时开发web项目为什么引入了spring-boot-starter-web这个起步依赖后,spring-boot-autoconfigure模块的web相关的自动配置类就会起自动起作用呢?

某个自动配置类起作用往往是由于classpath中存在某个类,这里以DispatcherServletAutoConfiguration这个自动配置类为切入点去分析。

先看下DispatcherServletAutoConfiguration能够自动配置的条件是啥?

1
2
3
4
5
6
7
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(DispatcherServlet.class)
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
public class DispatcherServletAutoConfiguration {
}

DispatcherServletAutoConfiguration能够自动配置的条件之一是@ConditionalOnClass(DispatcherServlet.class),即只有classpath中存在DispatcherServlet.class这个类,那么DispatcherServletAutoConfiguration自动配置相关逻辑才能起作用。

DispatcherServlet这个类是在spring-webmvc这个依赖库中的

再看下spring-boot-autoconfigure模块的pom.xml文件引入spring-webmvc这个依赖的情况:

1
2
3
4
5
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<optional>true</optional>
</dependency>

spring-boot-autoconfigure模块引入的spring-webmvc这个依赖时optional被设置为true,原来是可选依赖。即spring-webmvc这个依赖库只会被导入到spring-boot-autoconfigure模块中,而不会被导入到间接依赖spring-boot-autoconfigure模块的spring-boot-starter-web这个起步依赖中。

此时,再来看看spring-boot-starter-webpom.xml文件的依赖情况:

1
2
3
4
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>

spring-boot-starter-web起步依赖显式引入了spring-webmvc这个依赖库,即引入spring-webmvc时没有optional这个标签,又因为DispatcherServlet这个类是在spring-webmvc这个依赖库中的,从而classpath中存在DispatcherServlet这个类,因此DispatcherServletAutoConfiguration这个自动配置类就生效了。当然,web相关的其他自动配置类生效也是这个原理。

至此,明白了spring-boot-autoconfigure模块为什么要把引入的spring-webmvc这个依赖作为可选依赖了,其目的就是为了在spring-boot-starter-web起步依赖中能显式引入spring-webmvc这个依赖(这个起决定性作用),从而开发web项目只要引入了spring-boot-starter-web起步依赖,那么web相关的自动配置类就生效,从而可以开箱即用​这个就是spring-boot-starter-web这个起步依赖的构建原理了。

前面提到的spring-boot-starter-actuator,spring-boot-starter-test及其他内置的spring-boot-starter-xxx的起步依赖的构建原理也是如此,只不过spring-boot-starter-actuator依赖的是spring-boot-actuator-autoconfigurespring-boot-starter-test依赖的是spring-boot-test-autoconfigure模块罢了。

思考spring-boot-actuator-autoconfigurepom.xml文件引入了20多个可选依赖,而为什么spring-boot-starter-actuator起步依赖只引入了micrometer-core这个依赖呢?

4.模仿SpringBoot包结构自定义一个Starter

分析了SpringBoot内置的各种Starter的构建原理,我们可以自己动手实践一下自定义Starter

自定义starter的demo资料:

  1. https://github.com/jinyue233/mock-spring-boot-autoconfiguration
  2. https://github.com/mybatis/spring-boot-starter

总结

SpringBoot内置的各种Starter的构建原理分析就到此结束了,关键点总结:

  1. spring-boot-starter-xxx起步依赖没有一行代码,而是直接或间接依赖了xxx-autoconfigure模块,而xxx-autoconfigure模块承担了spring-boot-starter-xxx起步依赖自动配置的实现;
  2. xxx-autoconfigure自动配置模块引入了一些可选依赖,这些可选依赖不会被传递到spring-boot-starter-xxx起步依赖中,这是起步依赖构建的关键点
  3. spring-boot-starter-xxx起步依赖显式引入了一些对自动配置起作用的可选依赖;
  4. 经过前面3步的准备,项目只要引入了某个起步依赖后,就可以开箱即用了,而不用手动去创建一些bean等。

参考资料

  1. https://dayarch.top/p/maven-dependency-optional-transitive.html

评论加载中