探究自动装配原理
@SpringBootApplication
@SpringBootApplication
标注在某个类上说明这个类是 Spring Boot 的主配置类, Spring Boot 就应该运行这个类的 main
方法来启动 Spring Boot 应用;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
这里最上面四个注解的话没啥好说的,基本上自己实现过自定义注解的话,都知道分别是什么意思。关键就在于后面三个注解:
- @SpringBootConfiguration
- @ComponentScan
- @EnableAutoConfiguration
@SpringBootConfiguration
这个注解我们点进去就可以发现,它实际上就是一个 @Configuration 注解,这个注解大家应该很熟悉了,加上这个注解就是为了让当前类作为一个配置类交由 Spring 的 IOC 容器进行管理,因为 Spring Boot 本质上还是 Spring,所以原属于 Spring 的注解 @Configuration 在 Spring Boot 中也可以直接应用。
由此可见,@SpringBootConfiguration 注解的作用与 @Configuration 注解相同,都是标识一个可以被组件扫描器扫描的配置类,只不过 @SpringBootConfiguration 是被 Spring Boot 进行了重新封装命名而已。
@ComponentScan
这个注解也很熟悉,用于定义 Spring 的扫描路径,等价于在 xml 文件中配置 <context:component-scan>,假如不配置扫描路径,那么 Spring 就会默认扫描当前类所在的包及其子包中的所有标注了 @Component,@Service,@Controller 等注解的类。
@EnableAutoConfiguration
这个注解才是实现自动装配的关键,点进去之后发现,它是一个由 @AutoConfigurationPackage 和 @Import 注解组成的复合注解。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
看起来很多注解,实际上关键在 @Import 注解,它会加载 AutoConfigurationImportSelector类,然后就会触发这个类的 selectImports() 方法。根据返回的 String 数组(配置类的 Class 的名称)加载配置类。
我们重点看下 AutoConfigurationImportSelector。
AutoConfigurationImportSelector
AutoConfigurationImportSelector 中的selectImport 是自动装配的核心实现,它主要是读取 META-INF/spring.factories 文件,经过去重、过滤,返回需要装配的配置类集合。
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
我们点进 getAutoConfigurationEntry() 方法:
- getAttributes 获取 @EnableAutoConfiguration 中的 exclude、excludeName 等。
- getCandidateConfigurations 获取所有自动装配的配置类,也就是读取 spring.factories 文件,后面会再次说明。
- removeDuplicates 去除重复的配置项。
- getExclusions 根据 @EnableAutoConfiguration 中的 exclude、excludeName 移除不需要的配置类。
- fireAutoConfigurationImportEvents 广播事件。
- 最后根据多次过滤、判重返回配置类合集。
现在我们结合 getAutoConfigurationEntry()
的源码来详细分析一下:
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = getConfigurationClassFilter().filter(configurations);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
第 1 步:判断自动装配开关是否打开。
默认 spring.boot.enableautoconfiguration=true
,可在application.properties
中设置。
第 2 步 :
用于获取 EnableAutoConfiguration
注解中的 exclude
和 excludeName
。
第 3 步:
获取需要自动装配的所有配置类,读取 META-INF/spring.factories
。
我们点进 getCandidateConfigurations()
方法:
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
getBeanClassLoader());
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;
}
获取候选配置了使用了 Spring Framework 自定义的 SPI 机制,使用 SpringFactoriesLoader#loadFactoryNames 加载了类路径下 /META-INF/spring.factories 文件中的配置类,里面是以 key/value 形式存储,其中一个 key 是 EnableAutoConfiguration 类的全类名,而它的 value 是一个以 AutoConfiguration 结尾的类名的列表。以 spring-boot-autoconfigure 模块为例,其 spring.factories 内容如下。
不光是这个依赖下的 META-INF/spring.factories
被读取到,所有 Spring Boot Starter 下的 META-INF/spring.factories
都会被读取到。
第 4 步 :
到这里可能面试官会问你:“ spring.factories
中这么多配置,每次启动都要全部加载么?”。
很明显,这是不现实的。我们 debug 到后面你会发现,configurations
的值变小了。
虽然 133 个全场景配置项的自动配置启动的时候默认全部加载。但实际经过后续处理后只剩下 25 个配置项真正加载进来。很明显,Spring Boot 只会加载实际你要用到的场景中的配置类。这是如何做到的了?
按需加载
这里我们分析剩下的 25 个自动配置类,观察到每一个自动配置类都有着 @Conditional 或者其派生条件注解。
- @ConditionalOnBean:当容器里有指定 Bean 的条件下
- @ConditionalOnMissingBean:当容器里没有指定 Bean 的情况下
- @ConditionalOnSingleCandidate:当指定 Bean 在容器中只有一个,或者虽然有多个但是指定首选 Bean
- @ConditionalOnClass:当类路径下有指定类的条件下
- @ConditionalOnMissingClass:当类路径下没有指定类的条件下
- @ConditionalOnProperty:指定的属性是否有指定的值
- @ConditionalOnResource:类路径是否有指定的值
- @ConditionalOnExpression:基于 SpEL 表达式作为判断条件
- @ConditionalOnJava:基于 Java 版本作为判断条件
- @ConditionalOnJndi:在 JNDI 存在的条件下差在指定的位置
- @ConditionalOnNotWebApplication:当前项目不是 Web 项目的条件下
- @ConditionalOnWebApplication:当前项目是 Web 项 目的条件下
@Configuration(
proxyBeanMethods = false
)
// 检查是否有该类才会进行加载
@ConditionalOnClass({RedisOperations.class})
// 绑定默认配置信息
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
public RedisAutoConfiguration() {
}
...
}
所以当 classpath 下存在某一个 Class 时,某个配置才会生效。
小结
上面所有的注解都在做一件事:注册 bean 到 Spring 容器。他们通过不同的条件不同的方式来完成:
@SpringBootConfiguration 通过与 @Bean 结合完成 Bean 的 JavaConfig 配置;
@ComponentScan 通过范围扫描的方式,扫描特定注解注释的类,将其注册到 Spring 容器;
@EnableAutoConfiguration 通过 spring.factories 的配置,并结合 @Condition 条件,完成bean的注册;
@Import 通过导入的方式,将指定的 class 注册解析到 Spring 容器;
————————————————
原文链接:https://blog.youkuaiyun.com/D812359/article/details/126286516