最通俗易懂,最详细的springboot自动配置原理解析
tips:
从springboot的入门案例中,我们可以体会到springboot的便捷之处,使用Spring Initializer创建一个项目,然后写一个controller层就可以运行起来,我们啥也没配置,没配置tomcat、没配置mvc、没配置spring。。。。因为springboot底层都帮我们配置好了,而springboot的精髓就在于自动配置
然后不得不提一下springboot的四大特性:
- 自动装配
- Starter添加项目依赖
- Spring Boot CLI与Groovy的高效配合
- Spring Boot Actuator
本文主要讲解前两点(重点为自动装配)
Starter添加项目依赖:
先从入门案例的pom.xml开始
springboot的项目中都会存在一个父依赖,如下
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
该父项目为所有spring-boot-starter
的父项目
父项目有什么用?用来做依赖管理,托管子项目,按住Ctrl+鼠标左键点进去一探究竟
点进去之后发现spring-boot-starter
的父项目,还存在一个依赖。
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.3.2.RELEASE</version>
</parent>
再次点进去spring-boot-dependencies
会发现下图所示:
定义着所有依赖的版本和管理
所以我们能得出第一个结论:
spring-boot-dependencies
存放着SpringBoot的核心依赖,管理springboot应用里面的所有依赖版本。所以以后引入一些SpringBoot依赖的时候,不需要指定版本,但是没有在spring-boot-dependencies
当中管理的需要声明版本号。
那么这些依赖是如何导进来的呢?
再回到pom.xml中,pom.xml中还导入了如下依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
怎么理解spring-boot-starter-web
呢?
可以拆分为两部分来看spring-boot-starter
和web
spring-boot-starter
:spring的场景启动器
springboot将所有的功能场景都抽取出来,做成一个个starters(启动器),只需要在项目中引入这些starters相关场景的所有依赖都会导入进来,而且版本会自动控制。
Starters官方解释为一系列依赖描述的组合,可以通过导入这些Starters就会有相应的依赖
官网中:
有aop面向切面编程,amqp高级消息队列。。。需要啥导啥就完事了
再点进 spring-boot-starter-web
看看,声明了以下依赖:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</dependency>
<dependency> 做数据校验的
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
</dependencies>
原来是帮我们导入了web模块正常运行所需要依赖的组件
这个比较简单,只是开胃菜,我们再看下一个知识点
自动配置
每次启动都要通过这个主程序来启动,这个主程序上放了一个注解@SpringBootApplication
,此注解意为:标注在某个类上说明这个类是SpringBoot的主配置类,SpringBoot就应该运行这个类的main方法来启动SpringBoot应用;是我们研究的重点!!!
@SpringBootApplication
public class Springboot01HelloworldQuick03Application {
public static void main(String[] args) {
SpringApplication.run(Springboot01HelloworldQuick03Application.class, args);
}
}
接下来的分析由概括到详细,由浅到深。
先了解大概,再逐一详细分析
点进@SpringBootApplication
,发现是一个组合注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration //表示这是一个springboot的配置类
@EnableAutoConfiguration //开启自动配置功能
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
...
}
最重要两个注解就是@SpringBootConfiguration
和 @EnableAutoConfiguration
先看@SpringBootConfiguration
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
@AliasFor(
annotation = Configuration.class
)
boolean proxyBeanMethods() default true;
}
再看@EnableAutoConfiguration
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage //自动导包
@Import(AutoConfigurationImportSelector.class) //自动配置导入选择
public @interface EnableAutoConfiguration {
...
}
大致研究的方向:
@SpringBootConfiguration:
springboot的配置类,标注在某个类上,表示这是一个springboot的配置类
再点进去看一看:
@Configuration 标注在某个类上,表示这是一个springboot的配置类
@Configuration:
这个注解我们比较熟悉,spring中的配置类,相当于之前的xml配置文件
可以给容器中注入组件。。。以前配置文件的功能都可以做
此注解 @Configuration 也可以点进去,发现也是一个组件
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
String value() default "";
}
这个简单不做深究,后面为重点
@EnableAutoConfiguration:
顾名思义:开启自动配置功能,这个注解一定和自动配置相关,点进去看一下
@AutoConfigurationPackage //自动导包
@Import(AutoConfigurationImportSelector.class) //自动配置导入选择
这两个注解是我们研究的重点
@AutoConfigurationPackage:
点进此注解看一下
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
...
}
@Import为spring的注解,导入一个配置文件,在springboot中为给容器导入一个组件
导入的组件由AutoConfigurationPackages.Registrar.class执行逻辑来决定
再次点进去看看Registrar.class
并且可以打个断点,启动springboot,观察一下
然后可以计算一下new PackageImports(metadata).getPackageNames()
的值,
选中,右键
计算结果为com.liqiliang.springboot:
new PackageImport(metadata).getPackageName()
最后计算这个包名为com.liqiliang.springboot
所以可以得到如下结论:
@AutoConfigurationPackage这个注解本身的含义就是将主配置类(@SpringBootApplication标注的类)所在的包下面所有的组件都扫描到spring容器中,包名都是主配置类的
所以如果将HelloController放到com.liqiliang.springboot
包外面就扫描不到了,就会报错
@EnableAutoConfiguration的另一个注解:
@Import(AutoConfigurationImportSelector.class) //自动配置导入选择
AutoConfigurationImportSelector 开启自动配置类的导包的选择器(导入哪些组件的选择器)
点进去看一下
这个类中存在方法可以帮我们获取所有的配置
先做演示,再详解
此位置打断点,为了获取configurations的值
然后重启应用
一直往下执行,注意控制台是否出现configurations的值,
点开configurations,数组长度为127
注意看文件名,后缀全为 ***AutoConfiguration
将所有需要导入的组件以全类名的方式返回,并添加到容器中
最终会给容器中导入非常多的自动配置类(xxxAutoConfiguration),给容器中导入这个场景需要的所有组件,并配置好这些组件
有了自动配置类,免去了我们手动编写配置注入功能组件等工作
详解:
所有的配置都存放在configurations中,
而这些配置都从getCandidateConfigurations方法中获取,
这个getCandidateConfigurations方法是用来获取候选的配置。
这个方法可以用来获取所有候选的配置,那么这些候选的配置又是从哪来的呢?
点进getCandidateConfigurations方法
getCandidateConfiguration方法返回的list,这个list其实是由loadFactoryNames返回的,loadFactoryNames方法执行的时候传入了两个参数,一个参数为getSpringFactoriesLoaderFactoryClass(),此参数是什么?看下图
然后看到了一个眼熟的词 --->EnableAutoConfiguration,
再去loadFactoryNames方法中看看,看他带了EnableAutoConfiguration.class这个值做了什么坏事情
先是将EnableAutoConfiguration.class传给了factoryType,然后.getName( ),所以factoryTypeName值为EnableAutoConfiguration
然后调用loadSpringFactories( )方法,并且从类路径下得到一个资源
FACTORIES_RESOURCE_LOCATION值为什么?
前面成员位置定义好了
再回头看看loadFactoryNames方法的下面有一些话
如下位置 还有一些话,是一条断言
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct."
意思是:configurations必须非空,否则就打印一段话,No auto configuration classes found in META-INF/spring.factories
所以我们有必要去找找这个文件看看
(这个文件记好了,整个J2EE的自动配置组件都在springboot-autoconfigure的jar包中)
打开之后会发现里面包含了很多自动配置属性
比如点开webmvc的
然后会看到很多关于mvc的配置
那么扫描到的这些文件作用是什么呢:是把这个文件的urls拿到之后并把这些urls每一个遍历,最终把这些文件整成一个properties对象,继续看loadSpringFactories方法
然后从properties对象里边获取一些值,把这些获取到的值来加载我们最终要返回的这个结果
这个结果result为map集合,然后返回到loadFactoryNames方法中
这个factoryTypeName值为EnableAutoConfiguration
因为loadFactoryNames方法携带过来的第一个参数为EnableAutoConfiguration.class,所以factoryType值也为EnableAutoConfiguration.class,那么factoryTypeName值为EnableAutoConfiguration
那么map集合中getOrDefault方法为什么意思呢?意思就是当Map集合中有这个key时,就使用这个key值,如果没有就使用默认值defaultValue(第二个参数),所以是判断是否包含EnableAutoConfiguration
看下图,这不是包含着嘛
所以不得而知了,意为把spring-boot-autoconfigure-2.3.2.RELEASE.jar!/META-INF/spring.factories
这个文件下,EnableAutoConfiguration下面所有的组件,每一个xxxAutoConfiguration类都是容器中的一个组
件,都加入到容器中。
加入到容器中之后的作用就是用它们来做自动配置,这就是Springboot自动配置之源,也就是自动配置的开始,
只有这些自动配置类进入到容器中以后,接下来这个自动配置类才开始进行启动
那spring.factories中存在那么多的配置,每次启动时都是把它们全部加载吗?神经病吧,不现实的
我们随便点开一个类
再点开一个
是不是都有这个@ConditionalOnXXX注解,每一个类都有
@Conditional是spring底层注解,意思就是根据不同的条件,来进行自己不同的条件判断,如果满足指定的条件,那么整个配置类里边的配置才会生效。
所以又不得而知了,在加载自动配置类的时候,并不是将spring.factories的配置全量加载进来,而是通过这个注解的判断,如果注解中的类都存在,才会进行加载。
这样就实现了springboot的自动配置
小结一下:
从网上贴的流程:
SpringBoot在启动的时候从类路径下的META-INF/spring.factories中获取EnableAutoConfiguration指定的值
将这些值作为自动配置类导入容器 , 自动配置类就生效 , 帮我们进行自动配置工作;
以前我们需要自己配置的东西 , 自动配置类都帮我们解决了
整个J2EE的整体解决方案和自动配置都在springboot-autoconfigure的jar包中;
它将所有需要导入的组件以全类名的方式返回 , 这些组件就会被添加到容器中 ;
它会给容器中导入非常多的自动配置类 (xxxAutoConfiguration), 就是给容器中导入这个场景需要的所有组件 , 并配置好这些组件 ;
有了自动配置类 , 免去了我们手动编写配置注入功能组件等的工作;
简要概括一下自动装备:
启动类中有一个@SpringBootApplication注解,包含了@EnableAutoConfiguration代表开启自动
装配,@EnableAutoConfiguration注解会去spring-boot-autoconfigure工程下寻找 META-
INF/spring.factories 文件,这个文件中列举了所有自动装备类的清单,有一百多个,然后自动读取里
面的自动装配配置类清单。
但是这些类不会全部加载,很显然这样非常不合理。但是因为有@ConditionalOn
条件注解,满足
一定条件配置才会生效 如: @ConditionalOnClass(某类.class) 工程中必须包含一些相关的类时,
配置才会生效。所以说当我们的起步依赖中引入了一些对应的类之后,自动装备的条件满足了,自
动装备才会被触发。
本人刚接触springboot,草草的做了这么一个自动配置原理分析。有些表述不清楚或错误的地方请见谅。
最后说一句,springboot流弊!!!