前言
自动装配是 Spring Boot 最核心的功能之一,第三方可以基于这个特性非常方便的和 Spring 做整合,实现自己的 Starter,做到开箱即用。
Java 早期并不支持注解,所以那会儿 Spring 只能通过 xml 的形式来配置。早期项目里要引入一个功能模块,首先我们要引入 SDK,然后在 xml 里配置所需的 bean。随着引入的模块越来越多,开发者很快陷入 xml 的旋涡之中。
Spring 3.0 时代,官方开始提供@Import
注解实现自动装配的能力,同时也伴随了一堆以@EnableXXX
命名风格的注解,顾名思义加上这些注解就能启用对应的能力,这俩注解一般配合使用。
到了 Spring Boot 时代,自动装配再度迎来升级,它在@Import
基础上增加了 SPI 的能力,而且还支持条件装配,使用上更加灵活。
理解自动装配
什么是自动装配???
跟自动装配相对立的就是手动装配,早期我们通过 xml 手动往容器里注册 bean 的方式就是手动装配。手动装配的缺点是:
- 使用麻烦,需要维护一堆 xml
- 使用门槛高,开发者需要知道配置细节
反之,自动装配就是开发者根据 Spring Boot 定制的规范编写AutoConfiguration
类,Spring Boot 会自动加载这些配置类并把对应的 bean 注册到容器,这些 bean 是具备某种能力的,这样第三方就可以很轻松的把自己要提供的功能装进 Spring Boot。有了自动装配,开发者仅需加上少量注解或配置,甚至什么都不加(约定大于配置),就可以为项目引入一个功能模块。
装配的是什么???
从广义上理解,装配的是模块、是组件、是一个具体的功能。从狭义上理解,装配的其实是一个个具备某种能力的 bean。
自动装配是怎么实现的???
通过一个叫@EnableAutoConfiguration
的注解往容器导入了一个叫AutoConfigurationImportSelector
的类,它实现了ImportSelector
接口,Spring 启动时会触发子类方法按照规则加载自动装配类。
设计实现
在使用 Spring Boot 开发时,我们一般会在启动类上加@SpringBootApplication
注解,它就是自动装配的入口。
java
@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 {
......
}
它是一个复合注解,里面还集成了 Spring 提供的一些其它注解,从名字就能看出来,与自动装配有关的是@EnableAutoConfiguration
注解。
java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
@EnableAutoConfiguration
也是一个复合注解,它最核心的功能就是往容器导入了AutoConfigurationImportSelector
类。
AutoConfigurationImportSelector 实现了各种 Aware 接口,具备 BeanFactory、BeanClassLoader 等感知能力。最重要的是它实现了 DeferredImportSelector 接口,DeferredImportSelector 又继承自 ImportSelector。
ImportSelector 接口用来向容器注册批量导入配置类,子类重写selectImports()
返回要导入的类的全限定名数组:
java
public interface ImportSelector {
String[] selectImports(AnnotationMetadata importingClassMetadata);
@Nullable
default Predicate<String> getExclusionFilter() {
return null;
}
}
子接口 DeferredImportSelector 的区别是二者导入的时机不同,ImportSelector 会在@Configuration
Bean 处理前调用,DeferredImportSelector 会等处理完所有的@Configuration
Bean 之后再调用。
所以,Spring 启动时会先触发AutoConfigurationImportSelector.AutoConfigurationGroup#process
收集要导入的类,再触发selectImports()
返回导入项的迭代器。
java
@Override
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
Assert.state(deferredImportSelector instanceof AutoConfigurationImportSelector,
() -> String.format("Only %s implementations are supported, got %s",
AutoConfigurationImportSelector.class.getSimpleName(),
deferredImportSelector.getClass().getName()));
// 获取要导入的自动配置类
AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
.getAutoConfigurationEntry(annotationMetadata);
this.autoConfigurationEntries.add(autoConfigurationEntry);
for (String importClassName : autoConfigurationEntry.getConfigurations()) {
this.entries.putIfAbsent(importClassName, annotationMetadata);
}
}
核心是getAutoConfigurationEntry()
,它会按照 Spring Boot 的规范加载自动装配类,去重后再移除掉需要被排除的类,接着触发一个 AutoConfigurationImportListener 监听事件,最后返回收集到的类。
java
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
// 可以通过配置spring.boot.enableautoconfiguration=false来禁用自动装配
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 加载候选配置类
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 去重 转HashSet
configurations = removeDuplicates(configurations);
// 移除掉要排除的类
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = getConfigurationClassFilter().filter(configurations);
// 触发AutoConfigurationImportListener监听事件
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
Spring Boot 会按照什么规则加载配置类呢?方法是getCandidateConfigurations()
,它会加载候选的配置类,有两套加载规则:
- 读取 ClassPath 下
META-INF/spring.factories
文件里以org.springframework.boot.autoconfigure.EnableAutoConfiguration
为 Key 配置的类 - 读取 ClassPath 下
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件里配置的所有类
第一套规则更多的是给第三方提供的口子,第二套规则是 Spring Boot 导入内部配置类的口子。
java
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
/**
* 读取ClassPath下META-INF/spring.factories文件里
* 被org.springframework.boot.autoconfigure.EnableAutoConfiguration标注的类
*/
List<String> configurations = new ArrayList<>(
SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()));
/**
* 读取ClassPath下META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
* 文件里配置的类都要导入
*/
ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).forEach(configurations::add);
return configurations;
}
读取文件加载配置类用到了 Spring 提供的 SpringFactoriesLoader 类,代码不复杂,这里就不赘述了。
我们重点看一下,Spring Boot 内部都提供了哪些配置类,文件路径在spring-boot-autoconfigure/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
,官方默认提供了 144 个自动装配类,这里贴几个示例。
java
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration
......
这些配置类基本从名字就可以看出它们的功能,例如:AopAutoConfiguration 用来配置切面编程相关的 bean;RabbitAutoConfiguration 用来配置 RabbitMQ 相关的 bean。
自动配置类本身会作为 bean 注册到容器,除此之外还可以通过@Bean
注解方法的形式注册 bean。如果要提供的功能比较复杂,不想所有的代码都耦合在一个类里面,还可以在配置类上继续加@Import
注解引入另一个配置类。
条件配置
官方一股脑提供了 144 个自动配置类,我们不一定都需要啊,所以条件配置诞生了。
条件配置的意思是,可以给自动配置类加上一些前置条件,只有这些条件都满足了,配置类才会生效。条件配置伴随的是一堆以@ConditionalXXX
命名风格的注解,这里举几个常用的:
- @ConditionalOnBean:容器存在满足条件的 bean 才生效
- @ConditionalOnClass:容器存在满足条件的 Class 才生效
- @ConditionalOnMissingBean:容器不存在满足条件的 bean 才生效
- @ConditionalOnMissingClass:容器不存在满足条件的 Class 才生效
- @ConditionalOnWebApplication:必须是指定类型的 Web 应用环境才生效
有了条件配置,即使官方一股脑提供了一堆自动配置类,很多也都是不会生效的,要想生效我们得引入相关的依赖和配置。
以 RabbitAutoConfiguration 为例,它的生效条件是存在 RabbitTemplate.class、Channel.class,如果我们没有引入相关依赖,这些类肯定是不存在的,配置类自然也就不会生效了。
java
@AutoConfiguration
@ConditionalOnClass({ RabbitTemplate.class, Channel.class })
@EnableConfigurationProperties(RabbitProperties.class)
@Import({ RabbitAnnotationDrivenConfiguration.class, RabbitStreamConfiguration.class })
public class RabbitAutoConfiguration {
}
尾巴
所谓的自动装配,就是 Spring Boot 程序在启动时会去扫描 ClassPath 下的META-INF/spring.factories
文件,自动把 AutoConfiguration 类注册到容器,免去了开发者需要自己配置 bean 的麻烦过程。Spring Boot 一股脑提供了 144 个自动配置类,绝大多数开发者并不需要,所以提供了基于条件的自动配置,只有前置条件都满足了,配置类才会生效。基于自动装配和约定大于配置的设计理念,开发者仅需编写少量配置甚至不写任何配置就可以方便的引入一个功能模块。