传统配置装配机制
装配组件的三种方式:
- @Component 标记:只适用于装配自己编写的类
- @Configuration 标记配置类,在配置类中编写用 @Bean 标注的方法:适用于引入的第三方类库和自己编写的类
- @EnableXXX 与 @Import :适用模块装配,规避了配置分散,维护不灵活的问题
第三种模块装配主要用于解决注册过多,导致编码成本高,维护不灵活的问题(如果只是用@Component、@Configuration、@Bean这三种注解的话,很容易造成这些问题)
如何使用@EnableXXX和@Import
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
/** 用于导入@Configuration标注的类、ImportSelector实现类、ImportBeanDefinitionRegistrar实现类、普通的类
* {@link Configuration @Configuration}, {@link ImportSelector},
* {@link ImportBeanDefinitionRegistrar}, or regular component classes to import.
*/
Class<?>[] value();
}
从@Import的注释看出,它主要用于导入:
- @Configuration标注的类
- ImportSelector实现类
- ImportBeanDefinitionRegistrar实现类
- 普通的类
如何编写模块化配置
- 新建一个 @EnableColorConfig,使用@Import标记,在@Import上指定需要导入的配置
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import({Red.class, ColorRegistrarConfiguration.class, ColorImportSelector.class, ColorImportBeanDefinitionRegisrar.class})
public @interface EnableColor {
}
在这里,Red.class, ColorRegistrarConfiguration.class, ColorImportSelector.class, ColorImportBeanDefinitionRegisrar.class 分别对应着普通类、@Configuration标注的配置类、ImportSelector实现类、ImportBeanDefinitionRegistrar实现类
public class Red {
private String color;
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
}
@Configuration
public class ColorRegistrarConfiguration {
@Bean
public Yellow yellow() {
return new Yellow();
}
}
public class ColorImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{Blue.class.getName(), Green.class.getName()};
}
}
public class ColorImportBeanDefinitionRegisrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
registry.registerBeanDefinition("black", new RootBeanDefinition(Black.class));
}
}
springboot自动配置装配
对于一个springboot应用来说,我们只需要定义一个启动类即可,如下
// 1. 添加 @SpringBootApplication 注解标记
@SpringBootApplication
public class SpringbootDemoApplication {
public static void main(String[] args) {
// 2. 传入主配置类(在springboot内部称为 primarySource )调用 SpringApplication.run 方法
SpringApplication.run(SpringbootDemoApplication.class, args);
}
}
这样就构造了一个springboot应用。那么它是如何简化掉传统spring应用的各种配置的呢?我们从注解 @SpringBootApplication 入手。
@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,一个使用 @Configuration 标注的复合注解,用于声明这是一个springboot 应用的配置类
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}
- EnableAutoConfiguration,从名字上看,该注解负责启用自动化配置。自身也是一个复合注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
// 省略代码
}
由于 @EnableAutoConfiguration 本身又由注解 @AutoConfigurationPackage 标注,而且导入了AutoConfigurationImportSelector 配置类。详细下分析它的作用。
保存应用的根路径
首先是 @AutoConfigurationPackage 注解,通过 @Import 导入了ImportBeanDefinitionRegistrar实现类 AutoConfigurationPackages.Registrar
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
}
AutoConfigurationPackages.Registrar
/**
* 保存包的根路径
* {@link ImportBeanDefinitionRegistrar} to store the base package from the importing
* configuration.
*/
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 将启动类包名称设置到 BasePackages 类中,并且将 BasePackages 注册到 beanfactory
register(registry, new PackageImport(metadata).getPackageName());
}
@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImport(metadata));
}
}
AutoConfigurationPackages.register
// 以编程方式注册自动配置程序包名称
public static void register(BeanDefinitionRegistry registry, String... packageNames) {
if (registry.containsBeanDefinition(BEAN)) {
BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
constructorArguments.addIndexedArgumentValue(0, addBasePackages(constructorArguments, packageNames));
}
else {
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(BasePackages.class);
beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0, packageNames);
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(BEAN, beanDefinition);
}
}
BasePackages,对于它来说,自身被注册到 beanfactory 中,并且属性packages保存着应用启动类所在的包路径。
提供了 get 方法来获取这个包路径,方便在后续集成其他第三方组件时可以获取到这个包路径
static final class BasePackages {
private final List<String> packages;
private boolean loggedBasePackageInfo;
BasePackages(String... names) {
List<String> packages = new ArrayList<>();
for (String name : names) {
if (StringUtils.hasText(name)) {
packages.add(name);
}
}
this.packages = packages;
}
public List<String> get() {
if (!this.loggedBasePackageInfo) {
if (this.packages.isEmpty()) {
if (logger.isWarnEnabled()) {
logger.warn("@EnableAutoConfiguration was declared on a class "
+ "in the default package. Automatic @Repository and "
+ "@Entity scanning is not enabled.");
}
}
else {
if (logger.isDebugEnabled()) {
String packageNames = StringUtils.collectionToCommaDelimitedString(this.packages);
logger.debug("@EnableAutoConfiguration was declared on a class " + "in the package '"
+ packageNames + "'. Automatic @Repository and @Entity scanning is " + "enabled.");
}
}
this.loggedBasePackageInfo = true;
}
return this.packages;
}
}
自动化配置
由 ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry 方法触发 org.springframework.boot.autoconfigure.AutoConfigurationImportSelector.AutoConfigurationGroup#process 加载。
// AutoConfigurationImportSelector.AutoConfigurationGroup#process
@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(getAutoConfigurationMetadata(), annotationMetadata);
this.autoConfigurationEntries.add(autoConfigurationEntry);
for (String importClassName : autoConfigurationEntry.getConfigurations()) {
this.entries.putIfAbsent(importClassName, annotationMetadata);
}
}
最终会调用到以下方法,经过 SpringFactoriesLoader.loadFactoryNames 方法从类路径中寻找 META-INF/spring.factories 文件,获取到key为 org.springframework.boot.autoconfigure.EnableAutoConfiguration 的所有value。
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;
}
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
至于SpringFactoriesLoader的原理,其本质就是使用 ClassLoader 到类路径下加载文件 META-INF/spring.factories,解析为 key-value 的形式。类似于JDK 的 SPI 机制。