【“没有什么是一个断点不能解决的”系列】
一、Spring Boot自动配置原理
@SpringBootApplication
public class SpringbootLearningApplication {
public static void main(String[] args) {
// 1、返回我们的 IOC 容器,整个应用一启动就会给我们返回应用内的容器
ConfigurableApplicationContext run = SpringApplication.run(SpringbootLearningApplication.class, args);
// 2、查看容器里面的组件
String[] names = run.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}
// 获取容器内组件数量
int beanDefinitionCount = run.getBeanDefinitionCount();
System.out.println(beanDefinitionCount);
}
}
Ⅰ、引导加载自动配置类
@SpringBootApplication 注解表示这是一个 Spring Boot 应用。
进入源码分析可得,相当于以下这三个注解。
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.*******.*******")
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {}
1、@SpringBootConfiguration
底层用了 @Configuration 注解,代表只是一个配置类。
@Configuration
@Indexed
public @interface SpringBootConfiguration {}
2、@ComponentScan
包扫描注解,指定要扫描哪些。
3、@EnableAutoConfiguration
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {}
3.1、@AutoConfigurationPackage
@Import({Registrar.class}) // 给容器中导入一个组件 AutoConfigurationPackages.Registrar.class
public @interface AutoConfigurationPackage {}
利用 Registrar 给容器中导入了一系列组件。
那么是导入哪些组件呢?
- 我们进入到 Registrar 中 找到 registerBeanDefinitions 方法,可见传入了两个参数 注解元信息AnnotationMetadata metadata, BeanDefinitionRegistry registry。
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
AutoConfigurationPackages.register(registry, (String[])(new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames().toArray(new String[0]));
}
- 先来看 AnnotationMetadata metadata,因为该 Register 所在的 Import 注解标在 @AutoConfigurationPackage 注解上,而 @AutoConfigurationPackage 注解标在 @EnableAutoConfiguration 注解上,@EnableAutoConfiguration 注解最终又标在 XxxxxApplication上,因此分析得该注解最终是标在 XxxxxApplication上的。所以可以看到 metadata 注解元信息是标在应用程序类 XxxxxApplication 上。
- 把注解元信息拿到后,获取它所在的包名, (new AutoConfigurationPackages.PackageImports(metadata)).getPackageNames()。
- 然后把该包名封装在数组中,取数组中第一个元素,再 register 注册进去,相当于将该包下所有的组件批量注入。
因此是将 XxxxxApplication 程序所在的包下所有的组件批量导入进去。
这也解释了为什么默认的包路径是 XxxxxApplication 所在的包路径。
3.2、@Import({AutoConfigurationImportSelector.class})
进入 AutoConfigurationImportSelector,找到 selectImports 方法来分析要批量导入哪些组件,会返回要导入的组件的 String 数组。
在 selectImports 方法中利用 getAutoConfigurationEntry(annotationMetadata) 方法给容器中批量导入组件。之后将返回得到的 autoConfigurationEntry 再进一步得到所有的配置 autoConfigurationEntry.getConfigurations(),再转成数组作为 selectImports 方法的返回结果。
因此着重来分析 getAutoConfigurationEntry(annotationMetadata) 方法。
-
利用 getAutoConfigurationEntry(annotationMetadata) 方法给容器中批量导入组件;
-
在该方法中调用 List configurations = this.getCandidateConfigurations(annotationMetadata, attributes) 获取到所有需要导入到容器中的配置类(组件);
-
getCandidateConfigurations -> loadFactoryNames -> loadSpringFactories。
利用工厂加载器 Map<String, List> loadSpringFactories(ClassLoader classLoader) 得到所有的组件;
-
Enumeration urls = classLoader.getResources(“META-INF/spring.factories”);
又从 META-INF/spring.factories 位置加载文件。默认扫描当前系统里面所有 META-INF/spring.factories 位置的文件;
spring-boot-autoconfigure-2.5.0.jar 包中 META-INF/spring.factories 文件中就写有 131项(由第2步 getCandidateConfigurations 方法获取的 数组长度为 131 的 configurations),标明了要自动加载哪些自动配置类。
因此在此处 spring.factories 文件中写死了 spring-boot 一启动就要给容器中加载的所有配置类
虽然这131个场景的所有自动配置在项目启动的时候默认全部加载,但按照条件装配规则(@Conditional),最终会按需配置。
Ⅱ、按需开启自动配置项
- 虽然这131个场景的所有自动配置在项目启动的时候默认全部加载进容器
- 但按照条件装配规则(@Conditional),最终会按需配置,才会生效
可以暂时理解为带有 XxxxxAutoConfiguration 后缀的配置类文件在 spring-boot 一启动能够默认自动被加载进来到容器中。
1、以 AopAutoConfiguration 为例
首先来看 AopAutoConfiguration 类的注解
@Configuration 注解表示这是一个配置类;
@ConditionalOnProperty(prefix = “spring.aop”, name = {“auto”}, havingValue = “true”, matchIfMissing = true) 注解用来判断配置文件中是否存在 spring.aop.auto 为 true 的配置,如果存在的话该配置类就生效,并且 matchIfMissing = true 表示就算没有配值也默认表示 spring.aop.auto 为 true。
然后在 AopAutoConfiguration 类中,再来看 AspectJAutoProxyingConfiguration 类
@Configuration 表示其还是一个配置类;
@ConditionalOnClass({Advice.class}) 表示我们需要导入 org.aspectj.weaver.Advice 这个类的场景,AspectJAutoProxyingConfiguration {}类才能够生效
再来看 ClassProxyingConfiguration 类
@Configuration 表示是一个配置类;
@ConditionalOnMissingClass({“org.aspectj.weaver.Advice”}) 表示当系统中没有 org.aspectj.weaver.Advice 这个类时就会生效;
@ConditionalOnProperty( prefix = “spring.aop”, name = {“proxy-target-class”}, havingValue = “true”, matchIfMissing = true)表示配置文件中如果配了 spring.aop.proxy-target-class 为 true 的这个属性的话,那么就会生效,并且同样 matchIfMissing = true 表示就算没有配值也默认表示 spring.aop.proxy-target-class 为 true。
相当于最终我们的 ClassProxyingConfiguration 配置类,也开启了 aop 功能,开启的是简单的 aop 功能,而不是 AspectJ 的 aop。
2、以 CacheAutoConfiguration 为例
同样 CacheAutoConfiguration 也是 spring-boot 一启动默认自动加载进来的。
首先看 CacheAutoConfiguration 类
@Configuration 表示是一个配置类;
@ConditionalOnClass({CacheManager.class}) 表示在系统应用内有 org.springframework.cache.CacheManager 这个类文件导入情况下,才能生效;
@ConditionalOnBean({CacheAspectSupport.class}) 表示判断容器中是否有 CacheAspectSupport 类型的组件,可以通过以下代码来验证判断,发现容器中没有 CacheAspectSupport 类型的组件,因此以下都不会生效了;
String[] beanNamesForType = run.getBeanNamesForType(CacheAspectSupport.class); System.out.println("==========="+beanNamesForType.length); // 0
总结
Spring Boot 底层的设计模式之一:Spring Boot 默认会在底层配好所有的组件,但是如果用户自己配置了,以用户的优先。
@Bean
@ConditionalOnMissingBean // 如果系统中没有配 CharacterEncodingFilter,Spring Boot就会帮我们配
public CharacterEncodingFilter characterEncodingFilter() {}
public class MyConfig {
/**
* 自己配置 CharacterEncodingFilter,Spring Boot 会以我们自己配的优先
*/
@Bean
public CharacterEncodingFilter characterEncodingFilter() {
return null;
}
}
总结:
- Spring Boot 先加载所有的自动配置类;XxxxxAutoConfiguration
- 每个自动配置类按照条件进行生效(@Conditional);默认都会绑定配置文件 properties 文件指定的值;XxxxxProperties里面拿;XxxxxProperties 和配置文件进行了绑定
- 生效的配置类就会给容器中装配很多组件
- 只要容器中有这些组件,相当于就有了这些组件当中的功能
- 定制化配置
- 用户直接自己写个 @Bean 配置类组件替换底层的组件,会以用户配置的优先
- 用户查看这个组件获取的是配置文件中的什么值,直接去 properties 配置文件中进行修改
自动导入 XxxxxAutoConfiguration —> 按需配置组件 —> 从 XxxxxProperties 拿值 —> application.properties 中绑定了对应的值