SpringBoot自动配置原理
# SpringBoot 自动配置原理
# 1. SpringBoot 特点
依赖管理
- 父项目做依赖配置
- 开发导入 starter 场景启动器
- 无需关注版本号,自动版本仲裁
- 可以修改版本号
自动配置
- 自动配置 Tomcat
- 自动配置 SpringMVC
- 自动配置 Web 常见功能,如:字符编码问题
- 默认的包结构
- 主程序所在目录及其子目录下无需配置包扫描
- 或者使用其他包扫描注解扫描其他位置
- 各种配置拥有默认值
- 默认配置最终都映射到 MultipartProperties
- 配置文件的值最终会绑定每个类上,这个类会在容器中创建对象
- 按需加载所有的配置项
- 引入哪个 Starter 就加载哪个场景的依赖
- 自动配置功能都在
spring-boot-autoconfigure
内
- 底层会配置好所有的组件,如果用户自己配了,则使用用户自己的配置
- ConditionOnMissingBean
# 2. 组件添加
# 2.1 @Configuration
- 告诉 Spring Boot 这是一个配置类,等价于 xml 配置文件,默认时单例的
- 其本身也是一个组件
- proxyBeanMethods:代理 bean 方法
@Configuration(proxyBeanMethods = true)
:默认为 true- true:full 模式,内部方法调用时都会去容器中找组件,保持组件单例
- false:lite 模式,内部方法调用时都会创建新的组件
// 告诉 Spring 这是一个配置类,等价于配置文件
@Configuration
public class MyConfig {
// 给容器中添加组件,以方法名作为组件的 id,返回类型就是组件类型,返回值就是组件实例
@Bean
public User user01() {
return new User("zqc1");
}
@Bean
public User user02() {
return new User("zqc2");
}
@Bean("user03") // 直接指明 bean 的名称
public User xxx() {
return new User("zqc3");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
注意:单例 Bean 在容器中也可以有多个相同类型的 Bean,不过他们的 BeanName 不同。
上面这段代码供配置了 4 个组件:myConfig、user01、user02、user03
⭐ proxyBeanMethods
当 proxyBeanMethods = true
时,容器中只有一个 pet。
当 proxyBeanMethods = false
时,容器中只有一个 pet,user() 方法在 setPet 时会重新创建一个新的 pet,此时容器中的 pet 不是 user 所拥有的 pet。
@Configuration(proxyBeanMethods = true)
public class MyConfig {
@Bean
public User user() {
User zqc = new User("zqc");
zqc.setPet(pet());
return zqc;
}
@Bean
public Pet pet() {
return new Pet("cat");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# 2.2 @ComponentScan
扫描该报下的所有带 @Component、@Controller、@Service、@Repository 等注解的类,并将其中的组件注册到容器当中。
# 2.3 @Import
给容器中自动创建出该类型的组件
@Import({User.class, Pet.class })
@SpringBootApplication
public class SpringbootApplication {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(SpringbootApplication.class, args);
System.out.println(1);
}
}
2
3
4
5
6
7
8
9
10
容器启动后,可以在容器内找到 User 和 Pet 对应的组件,默认名字是全限定类名。
"com.zqc.springboot.bean.User" -> {User@4441}
"com.zqc.springboot.bean.Pet" -> {Pet@4447}
2
# 2.4 @Conditional
条件装配:满足 Contitional 指定的条件,则进行组件注入
举例说明:
- ConditionalOnBean:当容器中有这个组件的时候进行注入
- ConditionalOnMissBean:当容器中没有这个组件的时候进行注入
- ConditionalOnClass:当容器中有某个类的时候进行注入
使用 @ConditionalOnBean 进行测试:
@Configuration
public class MyConfig {
@Bean
public Pet pet() {
return new Pet("cat");
}
@ConditionalOnBean(name = "pet")
@Bean
public User user() {
User zqc = new User("zqc");
zqc.setPet(pet());
return zqc;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
此时容器启动,容器中将包含 pet 和 user 两个 bean 对象。
如果将 pet() 方法上的 @Bean 注解去掉,那么容器中将没有 pet 对象,由于条件没有满足,user 对象也不存在。
# 3. 原生配置文件导入
如果已经有 xml 配置文件,但又不像把他转成代码的形式,可以通过 @ImportResource
注解进行导入。
@Configuration
@ImportResource("classpath:beans.xml")
public class MyConfig {
...
}
2
3
4
5
# 4. 配置绑定
@ConfigurationProperties
如何使用 Java 读取到 properties 文件中的内容,并且把它封装到 JavaBean 中,以供随时使用:
// 只有在容器中的组件,才会拥有 SpringBoot 拥有的强大功能
@Component
@ConfigurationProperties(prefix = "mycar")
public class Car {
private String brand;
private Integer price;
...
}
2
3
4
5
6
7
8
配置文件
mycar.brand="BYD"
mycar.price=100000
2
当容器运行完成后,里面包含 car 的 bean 对象,且 brand 和 price 属性均有值。
也可以不使用 @Component
注解,可以在配置类中通过 @EnableConfigurationProperties(Car.class)
告诉 Spring 容器要把哪个组件自动注入到容器中。比如第三方的包,我们无法对其进行修改,此时可以使用 @EnableConfigurationProperties 注解。
# 5. SpringBoot 自动装配原理理解
pom.xml
SpringBoot 项目有一个 pom.xml,我们需要在里面配置一些需要的启动器 starter,后面会自动将这些 starter 的依赖装配进来,比如说spring-boot-starter-web
。
在这个 pom.xml 中也可以看到它的父项目spring-boot-parent
,它的父项目是 spring-boot-dependencies
,里面定义了当前 springboot 版本所自动装配依赖的版本,几乎包含了所有我们需要的依赖版本,这样我们在选择依赖的时候就不需要指定版本了。
启动器
Springboot 的启动场景
spring-boot-starter
:springBoot 的核心配置依赖- Core starter, including auto-configuration support, logging and YAML
比如,
spring-boot-starter-web
会帮我们自动导入 web 环境所有的依赖- Starter for building web, including RESTful, applications using Spring MVC. Uses Tomcat as the default embedded container
需要什么功能,就只需要找到对应的启动器。
主启动类
SpringBoot 有一个主启动类,需要注解 @SpringBootApplication
,自动装配的实现主要依赖这个注解来实现。其中包含了许多其他的注解,而这些注解实现的主要功能就是:
- 扫描启动类目录下的文件,如注解了 @Component、@Service、@Controller 的 bean。
- 加载自己的配置
- 自动加载的配置,通过一系列的操作读取
META-INF/spring.factorier
下的所有的自动加载配置类,对他们进行遍历加载。但并不是所有的类都会进行加载,在xxxAutoConfiguration
中会有@ConditionalOnXXX
,只有符合条件的才会被加载
总结
SpringBoot 先加载所有的自动配置类,xxxAutoConfiguration
每个自动配置类会按照条件进行生效,通常,生效后会绑定配置文件指定的值,xxxProperties,xxxProperties 与配置文件进行了绑定
生效的配置类就会给容器中装配了很多组件
只要容器中有这些组件,就有了对应的功能
如果用户定义了自己了自己的组件,则以用户配置的优先
定制化配置
- 用户创建新的 bean 组件,替换底层组件
- 用户去看这个组件获取了配置文件哪些值,去修改就可以了,就是修改 properties 或 xml 文件。
xxxAutoConfiguration --> 组件 ---> xxxProperties 里面取值 --> application.properties
# 6. 核心注解 @SpringBootApplication
SpringBoot 的核心注解:@SpringBootApplication 是合成注解,它可以看作 @SpringBootConfiguration
, @EnableAutoConfiguration
, @ComponentScan
注解的集合,这三个注解的作用分别是:
@EnableAutoConfiguration
:启动SpringBoot的自动配置@SpringBootConfiguration
:允许在上下文中注册额外的bean或者导入其他配置类@ComponentScan
:扫描被 @Component(@Service、@Controller)注解的bean,注解默认会扫描启动类所在的包下所有的类,可以自定义不扫描某些 bean。
SpringBoot 在启动时会扫描外部引用 jar 包中的
META-INF/spring.factories
文件,将文件中配置的类型信息加载到Spring容器,并执行类定义的各种操作。对于外部jar来说,只需按照 SpringBoot 定义的标准,就能将自己的功能装置进 Spring Boot。
# 7. @SpringBootConfiguration
...
@Configuration
public @interface SpringBootConfiguration {
@AliasFor(annotation = Configuration.class)
...
}
2
3
4
5
6
包含注解 @Configuration
,表示他是一个 Spring 的配置类。
...
@Component
public @interface Configuration {
...
}
2
3
4
5
包含注解 @Component
,表示他是 Spring 的组件
# 8. @EnableAutoConfiguration
自动配置的注解
...
@AutoConfigurationPackage //作用:将main包下的所有组件注册到容器中
@Import({AutoConfigurationImportSelector.class}) //加载自动装配类 xxxAutoconfiguration
public @interface EnableAutoConfiguration {
...
}
2
3
4
5
6
主要包含 @AutoConfigurationPackage
和 @Import({AutoConfigurationImportSelector.class})
# 8.1 @AutoConfigurationPackage
自动配置包,指定了默认包规则
...
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
}
2
3
4
可以看到,它是将AutoConfigurationPackages.Registrar.class
类注入到容器中,这个 Registrar 类可以帮我们导入一系列组件。
将一个指定包下的所有组件注册到 register,这个包其实就是主启动类所在的包下。
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
// AnnotationMetadata metadata 注解的元信息,可以看到通过它读取注解中的属性值
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
register(registry, new PackageImport(metadata).getPackageName());
}
@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImport(metadata));
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
⭐ metadata
说明
注解的元信息,可以看到通过它读取注解中的属性值。代表这个注解标在哪(就是指 AutoConfigurationPackage 注解),这个注解就是标在 SpringBoot 启动类上。
⭐ PackageImport(metadata).getPackageName()
说明
metadata 可以理解为指向主类,所以这段代码的意思就是获取主类所在的包的位置。
# 8.2 AutoConfigurationImportSelector
自动配置导入选择 @Import({AutoConfigurationImportSelector.class})
下面看看类 AutoConfigurationImportSelector
利用 selectImports()
方法得出需要导入哪些组件的数组,selectImports()
中的主要方法为 getAutoConfigurationEntry()
,给容器中批量导入一些组件。
public class AutoConfigurationImportSelector implements ... {
...
// 到底给容器中导入哪些
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
// 通过 getAutoConfigurationEntry() 批量导入一些组件
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
...
// 批量导入一些组件
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
...
// 获取所有候选配置
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
...
}
...
}
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
通过方法 getCandidateConfigurations()
来获取所有候选配置,可以看到该行代码运行完,configuration 中包含了 124 个数值,这 124 个候选组件默认导入到容器当中的。
下面详细看看 getCandidateConfigurations()
方法
// 获取所有候选配置
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
// 获取配置
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
getSpringFactoriesLoaderFactoryClass(), // 这里就是 EnableAutoConfiguration.class
getBeanClassLoader());
// 断言这个配置是否为空
// 如果为空,说明没有读取到 spring.factories
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.");
return configurations;
}
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
// SpringBoot的启动类标注了这个注解 (@SpringBootApplication 包含 @EnableAutoConfiguration)
return EnableAutoConfiguration.class;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
使用 SpringFactoriesLoader.loadFactoryNames()
方法,该方法又调用了 loadSpringFactories()
得到所有的组件
// 获取所有的加载配置
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
// 这里的 factoryType 就是 EnableAutoConfiguration.class
// 所以,就是启动类下的所有资源被导入
String factoryTypeName = factoryType.getName();
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
// 加载配置文件
// 上面的方法调用了这个方法
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
// FACTORIES_RESOURCE_LOCATION:"META-INF/spring.factories",里面包含了所有的自动配置类
Enumeration<URL> urls = (classLoader != null ?
// 从项目资源中获取
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
// 从系统资源中获取
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
// 遍历了所有自动配置,封装成 Properties 供我们使用
while (urls.hasMoreElements()) { // 判断有没有更多的元素
URL url = urls.nextElement(); // 获取 url
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryTypeName = ((String) entry.getKey()).trim();
for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryTypeName, factoryImplementationName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
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
44
loadSpringFactories()
方法就是通过扫描 META-INF/spring.factories
位置获取所有自动配置类。也就是在这个配置文件中写死了 Spring 启动时要加载的所有配置类。虽然全部加载进来了,但是不一定会生效,会按需开启。
META-INF/spring.factories
:自动配置的核心文件,它在 spring-boot-autoconfigure-版本\META-INF\spring.factories
下,所有的自动配置类都在这里
# Initializers 初始化
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
# Application Listeners 监听
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer
# Auto Configuration Import Listeners 自动选择导入的包
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener
# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition
# Auto Configure 自动配置
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
...
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
获取所有符合条件的类的全限定类名,这些类需要被加载到IoC容器中。
# 9. @ConditionalOnXXX
为什么导入了这么多自动配置没有生效?需要导入对应的 start 才能生效?
通过注解 @ConditionalOnXXX
判断条件是否满足,如果其中的条件都满足才会生效。当没有导入对应的 starter,就没有对用的 jar 包,此时 @ConditionalOnXXX
就不会生效。