ConfigurationProperties 注解
@Import、@ImportAutoConfiguration
当我们需要在一个配置类中导入另一个 Configuration 配置类时,可以使用 @Import
、@ImportAutoConfiguration
注解:
@Configuration
@Import(MyOtherAutoConfiguration.class)
public class MyAutoConfiguration implements ImportAware {
}
@Configuration
@ImportAutoConfiguration(MyOtherAutoConfiguration.class)
public class MyAutoConfiguration implements ImportAware{
}
查看源码可以发现 @ImportAutoConfiguration
继承了 @Import
注解,所以核心是这个注解:
@Import
注解主要是用来替代 Spring XML 中的 <import/>
标签的,这个标签主要是用来解决配置混乱的,它支持我们将要声明的 bean 分类写入多个 XML 配置文件中。通过 @Import
能够将多个配置类集中整合到一起。但是 @Import
的使用场景十分局限,比如 (1)它导入的配置类集合是固定的无法根据实际情况选择;(2)Import导入的类只能在当前模块所依赖的下游模块中,无法导入一个上游或者第三方配置类。
但是 ImportSelector
接口可以解决上面两个缺陷,它会根据导入的 ImportSelector
实现类所返回的值作为需要导入的配置类集合,并对这些类加载。实例如下,重写 selectImports()
方法,通过获取环境变量中的配置,选择导入哪个类。
// 自定义类选择导入
@Configuration
@Import({MyImportSelector.class})
public class MyAutoConfiguration implements ImportAware {
}
public class MyImportSelector implements ImportSelector, EnvironmentAware {
private Environment environment;
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
boolean use1 = environment.getProperty("use1", Boolean.class, false);
String configClass = use1 ? "com.xxx.demo.extension.MyOtherConfiguration1" : "com.xxx.demo.extension.MyOtherConfiguration2";
return new String[]{configClass};
}
@Override
public void setEnvironment(Environment env) {
environment = env;
}
}
同样还有另外一种方式,那就是 ImportBeanDefinitionRegistrar
,它的接口中直接提供了 BeanDefinitionRegistry
参数,也就意味着可以直接自主地进行 Bean注册,而不是返回一个类名或者类对象。
@Configuration
@Import({MyImportBeanDefinitionRegistry.class})
public class MyAutoConfiguratoin implements ImportAware {
}
public class MyImportBeanDefinitionRegistry implements ImportBeanDefinitionRegistrar, EnvironmentAware {
private Environment environment;
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
boolean use1 = environment.getProperty("use1", Boolean.class, false);
String configClass = use1 ? "com.xxx.demo.extension.MyOtherConfiguration1" : "com.xxx.demo.extension.MyOtherConfiguration2";
BeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClassName(configClass);
registry.registryBeanDefinition("myOtherConfiguration", beanDefinition);
}
}
细看 @ImportAutoConfiguration注解
查看 ImportAutoConfiguration
注解,我们可以发现它继承了 ImportAutoConfigurationImportSelector.class
类,进入这个类源码可以发现它继承了 AutoConfigurationImportSelector
类。
AutoConfigurationImportSelector
类内部实现了 selectImports()
方法,该方法返回的全限定类名会被加载进 Bean对象池。
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
// 核心通过这个方法获取候选类名
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
configurations = this.removeDuplicates(configurations);
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = this.getConfigurationClassFilter().filter(configurations);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
}
}
}
getCandidateConfigurations()
方法在 ImportAutoConfigurationImportSelector
类中重新实现了,代码如下:
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> candidates = new ArrayList();
Map<Class<?>, List<Annotation>> annotations = this.getAnnotations(metadata);
annotations.forEach((source, sourceAnnotations) -> {
this.collectCandidateConfigurations(source, sourceAnnotations, candidates);
});
return candidates;
}
private void collectCandidateConfigurations(Class<?> source, List<Annotation> annotations, List<String> candidates) {
Iterator var4 = annotations.iterator();
while(var4.hasNext()) {
Annotation annotation = (Annotation)var4.next();
candidates.addAll(this.getConfigurationsForAnnotation(source, annotation));
}
}
private Collection<String> getConfigurationsForAnnotation(Class<?> source, Annotation annotation) {
String[] classes = (String[])AnnotationUtils.getAnnotationAttributes(annotation, true).get("classes");
// 如果ImportAutoConfiguration 注解指定了属性值,那么直接返回 classes的值;如果没有配置属性值,就从 spring.factories 文件中读取 key 为当前配置类名的结果
return (Collection)(classes.length > 0 ? Arrays.asList(classes) : this.loadFactoryNames(source));
}
所以总结如下:
- 先获取当前配置类上的 ImportAutoConfiguration 注解信息;
- 如果 ImportAutoConfiguration 注解中指定了classes(或value) 属性值,那么直接返回 classes 的值;
- 如果未配置 classes(或value) 属性值,从 spring.factories 文件中读取key为当前配置类名的结果;
@ImportAutoConfiguration
注解使用实例
该注解的使用有以下两种方式:
方式一:直接在注解中绑定
方式二:注解中不指定,在 spring.factories
中指定