SpringBoot内置的各种Starter是怎样构建的?--SpringBoot源码(六)
注:该源码分析对应SpringBoot版本为2.1.0.RELEASE
1 温故而知新
本篇接 外部配置属性值是如何被绑定到XxxProperties类属性上的?--SpringBoot源码(五)
温故而知新,我们来简单回顾一下上篇的内容,上一篇我们分析了SpringBoot外部配置属性值是如何被绑定到XxxProperties类属性上的相关源码,现将外部属性绑定的重要步骤总结如下:
- 首先是
@EnableConfigurationProperties
注解import
了EnableConfigurationPropertiesImportSelector
后置处理器; EnableConfigurationPropertiesImportSelector
后置处理器又向Spring
容器中注册了ConfigurationPropertiesBeanRegistrar
和ConfigurationPropertiesBindingPostProcessorRegistrar
这两个bean
;- 其中
ConfigurationPropertiesBeanRegistrar
向Spring
容器中注册了XxxProperties
类型的bean
;ConfigurationPropertiesBindingPostProcessorRegistrar
向Spring
容器中注册了ConfigurationBeanFactoryMetadata
和ConfigurationPropertiesBindingPostProcessor
两个后置处理器; ConfigurationBeanFactoryMetadata
后置处理器在初始化bean
factory
时将@Bean
注解的元数据存储起来,以便在后续的外部配置属性绑定的相关逻辑中使用;ConfigurationPropertiesBindingPostProcessor
后置处理器将外部配置属性值绑定到XxxProperties
类属性的逻辑委托给ConfigurationPropertiesBinder
对象,然后ConfigurationPropertiesBinder
对象又最终将属性绑定的逻辑委托给Binder
对象来完成。
可见,重要的是上面的第5步。
2 引言
我们都知道,SpringBoot内置了各种Starter
起步依赖,我们使用非常方便,大大减轻了我们的开发工作。有了Starter
起步依赖,我们不用去考虑这个项目需要什么库,这个库的groupId
和artifactId
是什么?更不用担心引入这个版本的库后会不会跟其他依赖有没有冲突。
举个栗子:现在我们想开发一个web项目,那么只要引入
spring-boot-starter-web
这个起步依赖就可以了,不用考虑要引入哪些版本的哪些依赖了。像以前我们还要考虑引入哪些依赖库,比如要引入spring-web
和spring-webmvc
依赖等;此外,还要考虑引入这些库的哪些版本才不会跟其他库冲突等问题。
那么我们今天暂时不分析SpringBoot自动配置的源码,由于起步依赖跟自动配置的关系是如影随形的关系,因此本篇先站在maven项目构建的角度来宏观分析下我们平时使用的SpringBoot内置的各种Starter
是怎样构建的?
3 Maven传递依赖的optional标签
在分析SpringBoot内置的各种Starter
构建原理前,我们先来认识下Maven的optional
标签,因为这个标签起到至关重要的作用。
Maven的optional
标签表示可选依赖即不可传递的意思,下面直接举个栗子来说明。
比如有A
,B
和C
三个库,C
依赖B
,B
依赖A
。下面看下这三个库的pom.xml
文件:
// A的pom.xml
<?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>
<?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>
<?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
,B
和C
库的pom.xml
可知,B
库依赖A
库,然后C
库又依赖了B
库,那么请想一下,Maven打包构建C
库后,A
库有没有被引进来?
答案肯定是没有,因为B
库引入A
库依赖时使用了<optional>true</optional>
,即将Maven的optional
标签值设为了true
,此时C
库再引入B
库依赖时,A
库是不会被引入到C
库的。
同时跟Maven传递依赖有关的还有一个exclusions
标签,这个表示将某个库的某个子依赖排除掉,这里不再详述。
4 SpringBoot内置的各种Starter是怎样构建的?
我们现在来探究SpringBoot内置的各种Starter
到底是怎样构建的呢?
还记得如何分析SpringBoot源码模块及结构?这篇文章分析的SpringBoot内部的模块之间的关系吗?先来回顾一下SpringBoot源码内部模块图:
图1
我们都知道,SpringBoot的Starter
的构建的原理实质就是自动配置,因此由图1可以看到SpringBoot源码项目内部跟Starter
及其自动配置有关的模块有四个:spring-boot-starters
,spring-boot-actuator-autoconfigure
,spring-boot-autoconfigure
和spring-boot-test-autoconfigure
。 每个模块的作用请看如何分析SpringBoot源码模块及结构?这篇文章,这里不再赘述。
那么,spring-boot-starters
模块跟后面三个自动配置有关的模块xxx-autoconfigure
模块的关系是怎样的呢?
此时我们先来看看spring-boot-starters
模块里面的结构是怎样的?
图2
由图2可以看到spring-boot-starters
模块包含了SpringBoot内置的各种starter
:spring-boot-starter-xxx
。由于SpringBoot内置的各种starter
太多,以我们常用的spring-boot-starter-web
起步依赖来探究好了。
我们首先看下spring-boot-starter-web
模块内部结构:
图3
可以看到spring-boot-starter-web
模块里面只有.flattened-pom.xml
和pom.xml
文件,而没有任何代码!有点出乎我们意料。我们都知道若要用到SpringBoot的web功能时引入spring-boot-starter-web
起步依赖即可,而现在spring-boot-starter-web
模块里面没有一行代码,那么spring-boot-starter-web
究竟是如何构建的呢?会不会跟图1所示的spring-boot-autoconfigure
自动配置模块有关?
此时我们就需要看下spring-boot-starter-web
模块的pom.xml
文件内容:
图4
由图4可以看到,spring-boot-starter-web
模块依赖了spring-boot-starter
,spring-boot-starter-tomcat
,spring-web
和spring-webmvc
等模块,居然没有依赖spring-boot-autoconfigure
自动配置模块!
由于spring-boot-starter-web
模块肯定跟spring-boot-autoconfigure
自动配置模块有关,所以spring-boot-starter-web
模块肯定是间接依赖了spring-boot-autoconfigure
自动配置模块。
图4标有标注"重点关注"的spring-boot-starter
模块是绝大部分spring-boot-starter-xxx
模块依赖的基础模块,是核心的Starter
,包括了自动配置,日志和YAML
支持。我们此时来关注下spring-boot-starter
的pom.xml
文件,也许其依赖了了spring-boot-autoconfigure
自动配置模块。
图5
由图5可以看到,我们前面的猜想没有错,正是spring-boot-starter
模块依赖了spring-boot-autoconfigure
自动配置模块!因此,到了这里我们就可以得出结论了:spring-boot-starter-web
模块没有一行代码,但是其通过spring-boot-starter
模块间接依赖了spring-boot-autoconfigure
自动配置模块,从而实现了其起步依赖的功能。
此时我们再来看下spring-boot-autoconfigure
自动配置模块的内部包结构:
图6
由图6红框处,我们可以知道spring-boot-starter-web
起步依赖的自动配置功能原来是由spring-boot-autoconfigure
模块的web
包下的类实现的。
到了这里spring-boot-starter-web
起步依赖的构建基本原理我们就搞清楚了,但是还有一个特别重要的关键点我们还没Get到。这个关键点跟Maven的optional
标签有的作用有关。
为了Get到这个点,我们先来思考一个问题:平时我们开发web
项目为什么引入了spring-boot-starter-web
这个起步依赖后,spring-boot-autoconfigure
模块的web
相关的自动配置类就会起自动起作用呢?
我们应该知道,某个自动配置类起作用往往是由于classpath
中存在某个类,这里以DispatcherServletAutoConfiguration
这个自动配置类为切入点去Get这个点好了。
先看下DispatcherServletAutoConfiguration
能够自动配置的条件是啥?
图7
由图7所示,DispatcherServletAutoConfiguration
能够自动配置的条件之一是@ConditionalOnClass(DispatcherServlet.class)
,即只有classpath
中存在DispatcherServlet.class
这个类,那么DispatcherServletAutoConfiguration
自动配置相关逻辑才能起作用。
而DispatcherServlet
这个类是在spring-webmvc
这个依赖库中的,如下图所示:
图8
此时我们再看下spring-boot-autoconfigure
模块的pom.xml
文件引入spring-webmvc
这个依赖的情况:
图9
由图9所示,spring-boot-autoconfigure
模块引入的spring-webmvc
这个依赖时optional
被设置为true
,原来是可选依赖。即spring-webmvc
这个依赖库只会被导入到spring-boot-autoconfigure
模块中,而不会被导入到间接依赖spring-boot-autoconfigure
模块的spring-boot-starter-web
这个起步依赖中。
此时,我们再来看看spring-boot-starter-web
的pom.xml
文件的依赖情况:
图10
由图10所示,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-autoconfigure
,spring-boot-starter-test
依赖的是spring-boot-test-autoconfigure
模块罢了,这里不再详述。
思考:
spring-boot-actuator-autoconfigure
的pom.xml
文件引入了20多个可选依赖,而为什么spring-boot-starter-actuator
起步依赖只引入了micrometer-core
这个依赖呢?
5 模仿SpringBoot包结构自定义一个Starter
前面分析了SpringBoot内置的各种Starter
的构建原理,理论联系实践,那么如果能够动手实践一下自定义Starter
那就更好了。
下面提供一个自定义Starter
的一个简单Demo
,这个Demo
完全模仿SpringBoot
内置Starter
的内部包结构来编写,对于进一步了解SpringBoot内置的各种Starter
的构建原理很有帮助。
下面是这个Demo
的github地址,推荐给有兴趣的小伙伴们。
模仿springboot内部结构自定义Starter。此外,如何自定义一个Starter
,可以参考下Mybatis的spring-boot-starter是如何编写的。
6 小结
好了,SpringBoot内置的各种Starter
的构建原理分析就到此结束了,现将关键点总结下:
spring-boot-starter-xxx
起步依赖没有一行代码,而是直接或间接依赖了xxx-autoconfigure
模块,而xxx-autoconfigure
模块承担了spring-boot-starter-xxx
起步依赖自动配置的实现;xxx-autoconfigure
自动配置模块引入了一些可选依赖,这些可选依赖不会被传递到spring-boot-starter-xxx
起步依赖中,这是起步依赖构建的关键点;spring-boot-starter-xxx
起步依赖显式引入了一些对自动配置起作用的可选依赖;- 经过前面3步的准备,我们项目只要引入了某个起步依赖后,就可以开箱即用了,而不用手动去创建一些
bean
等。
原创不易,帮忙点个赞呗!
由于笔者水平有限,若文中有错误还请指出,谢谢。
参考:
1,Maven 依赖传递性透彻理解
欢迎关注【源码笔记】公众号,一起学习交流。
SpringBoot内置的各种Starter是怎样构建的?--SpringBoot源码(六)的更多相关文章
- 使用外部容器运行spring-boot项目:不使用spring-boot内置容器让spring-boot项目运行在外部tomcat容器中
前言:本项目基于maven构建 spring-boot项目可以快速构建web应用,其内置的tomcat容器也十分方便我们的测试运行: spring-boot项目需要部署在外部容器中的时候,spring ...
- Spring-Boot 内置静态资源文件地址修改
Spring-Boot 内置MVC静态文件地址修改 Why:1.Spring-Boot修改内置SpringMVC静态资源路径,提高项目目录结构的安全性.2.配置拦截路径时可以剔除静态文件拦截How:1 ...
- log4j日志相对路径,Tomcat(第三方和Springboot内置)参数catalina.home和catalina.base的设置
关于Log4j日志相对路径的配置请看:log4j 产生的日志位置设置 和 catalina.home.catalina.base . 由于我们在Log4j的配置中引入了系统属性${catalina.b ...
- springboot内置tomcat验证授权回调页面域名
springboot内置tomcat验证公众号授权回调页面域名 解决方法: 网上下载一个tomcat,在server.xml文件中修改端口为springboot内置tomcat的端口号,复制验证文件到 ...
- springboot+内置改为外置tomcat
1.pom.xml springboot项目利用的是自己内置的tomcat,这边就是不依赖内置的tomcat,将其编译的作用域设置为provided <dependency> <gr ...
- SpringBoot内置生命周期事件详解 SpringBoot源码(十)
SpringBoot中文注释项目Github地址: https://github.com/yuanmabiji/spring-boot-2.1.0.RELEASE 本篇接 SpringBoot事件监听 ...
- SpringBoot内置tomcat启动原理
前言 不得不说SpringBoot的开发者是在为大众程序猿谋福利,把大家都惯成了懒汉,xml不配置了,连tomcat也懒的配置了,典型的一键启动系统,那么tomcat在springb ...
- springboot - 应用实践(N)使用springboot内置的@Scheduled
1.springboot开箱即用,内置调度任务的使用. 建一个简单的springboot工程,pom.xml: <?xml version="1.0" encoding=&q ...
- 去除springboot内置tomcat
/** * @author zx * @title: ServletInitializer * @projectName activiti * @description: 解决内置tomcat * @ ...
随机推荐
- deeplearning.ai 卷积神经网络 Week 2 卷积神经网络经典架构
1. Case study:学习经典网络的原因是它们可以被迁移到其他任务中. 1.1)几种经典的网络: a)LeNet-5(LeCun et al., 1998. Gradient-based lea ...
- FPGA模N计数器的实现
module ModuloN_Cntr(Clock, Clear, Q, QBAR); , UPTO = ;//计数器位数以及模数 input Clock, Clear; :]Q, QBAR; :]C ...
- 基于Docker 部署Jmeter + Grafana + InfluxDB 性能测试监控配置(亲测可用)
工具介绍: InfluxDB:是一款用Go语言编写的开源分布式时序.事件和指标数据库,无需外部依赖.该数据库现在主要用于存储涉及大量的时间戳数据,如DevOps监控数据,APP metrics, lo ...
- Nginx笔记总结十四: nginx反向代理,用内网域名转发
user www www; worker_processes ; error_log logs/error.log; pid logs/nginx.pid; worker_rlimit_nofile ...
- 使用apache mail发送邮件错误解决办法
今天在写发送邮件的程序时发现了以下两个些错误,贴出来跟大家分享分享 希望对大家有帮助. 错误一: Exception in thread "main" java.lang.NoCl ...
- pattern space and hold space of sed
Copied from: stackoverflow When sed reads a file line by line, the line that has been currently read ...
- Blue的博客
整合其他ORM框架 使用Spring所提供的ORM整合方案, 可以获得许多好处: 方便基础设施的搭建 Spring中, 对不同的ORM框架, 首先, 始终可以采用相同的方式配置数据源; 其次, Spr ...
- springboot利用swagger构建api文档
前言 Swagger 是一款RESTFUL接口的文档在线自动生成+功能测试功能软件.本文简单介绍了在项目中集成swagger的方法和一些常见问题.如果想深入分析项目源码,了解更多内容,见参考资料. S ...
- java内存区域----运行时数据区
Java虚拟机的内存区域也叫做java运行时数据区,共分为五个部分:程序计数器,方法区,本地方法栈,虚拟机栈和堆.方法区和堆是线程之间所共有的,程序计数器,本地方法栈,虚拟机栈是线程私有的.其中虚拟机 ...
- 《前端面试加分项目》系列 企业级Vue瀑布流
本文 GitHub github.com/ponkans/F2E 已收录,有一线大厂面试点思维导图,也整理了很多我的文档,欢迎Star和完善,大家面试可以参照考点复习.文末有福利~~ 前言 接水怪又来 ...