自动配置类原理
一些公用或通用性的类或第三方的配置类,不需要每个项目都重复的编写,将他们抽取成自动配置类,使用的时候只需要引入即可;
代码实现:
public class A14 {
public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean("config",Config.class);
//添加BeanFactory后处理器,解析注解
context.registerBean(ConfigurationClassPostProcessor.class);
context.refresh();
for (String definitionName : context.getBeanDefinitionNames()) {
System.out.println(definitionName);
}
}
@Configuration //项目的配置,这里将第三方的配置类,引入到本项目中
@Import({AutoConfiguration1.class, AutoConfiguration2.class})
static class Config {
}
@Configuration //第三方的配置类
static class AutoConfiguration1 {
@Bean
public Bean1 bean1() {
return new Bean1();
}
}
@Configuration //第三方的配置类
static class AutoConfiguration2 {
@Bean
public Bean2 bean2() {
return new Bean2();
}
}
static class Bean1 {
}
static class Bean2 {
}
}
优化:
我们在引入配置类的时候可以只引入一个类,该类帮我们去引入第三方配置类,代码如下:
@Configuration //项目的配置,这里将第三方的配置类,引入到本项目中
@Import({MyImportSelector.class})
static class Config {
}
static class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{AutoConfiguration1.class.getName(), AutoConfiguration2.class.getName()};
}
}
再次优化:
我们引入的第三方配置类名最好写在配置文件中,然后通过读取配置文件,引入第三方配置类,代码如下:
先在 resource 目录下新建一个 META-INF 目录,在该目录下新建一个 spring.factories 文件,在该文件中写入要引入的配置类全类名,
需要注意的是,如果配置类是内部类,需要用 $
符。
com.wang.work.A14$MyImportSelector=\
com.wang.work.A14.AutoConfiguration1,\
com.wang.work.A14.AutoConfiguration2
@Configuration //项目的配置,这里将第三方的配置类,引入到本项目中
@Import({MyImportSelector.class})
static class Config {
}
static class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
List<String> names = SpringFactoriesLoader.loadFactoryNames(MyImportSelector.class, null);
return names.toArray(new String[0]);
}
}
注意:
-
上述实现的 ImportSelector 接口,如果第三方的配置类和本项目的配置类重名,配置类会被覆盖,SpringBoot启动会先加载第三方的配置类,然后加载本项目的配置类,所以最后加载的是本项目的配置类;也可以关闭自动覆盖(SpringBoot 中默认是关闭的),不过有相同的配置类时会抛出异常。
-
SpringBoot启动会先加载第三方的配置类,然后加载本项目的配置类这样并不合理,加载顺序应该先加载本项目的,然后再加载第三方的才合理,这时,只需要将 ImportSelector 接口改为 DeferredImportSelector 接口即可;为了防止配置类冲突,在第三方配置类上加上 @ConditionalOnMissingBean 注解即可(SpringBoot 中就是这样做的),冲突之后就不会加载第三方的配置类。
static class MyImportSelector implements DeferredImportSelector { @Override public String[] selectImports(AnnotationMetadata importingClassMetadata) { List<String> names = SpringFactoriesLoader.loadFactoryNames(MyImportSelector.class, null); return names.toArray(new String[0]); } } @Configuration //第三方的配置类 static class AutoConfiguration1 { @Bean @ConditionalOnMissingBean public Bean1 bean1() { return new Bean1(); } }
SpringBoot 自动配置源码分析:
- @SpringBootApplication 注解是一个复合注解,其内部 @EnableAutoConfiguration 注解就是开启自动配置
@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 {
- @EnableAutoConfiguration 注解内部 @Import(AutoConfigurationImportSelector.class) 会通过加载AutoConfigurationImportSelector 来读取 spring.factories 配置文件里的配置类
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
- AutoConfigurationImportSelector 实现了 DeferredImportSelector,重写了 selectImports() 方法,getAutoConfigurationEntry() 方法内部会调用 getCandidateConfigurations() 方法,使用 spring.factories 文件加载器去加载 以AutoConfiguration 为键的所有配置类
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
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);
}
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
AnnotationAttributes attributes) {
List<String> configurations = new ArrayList<>(
SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
getBeanClassLoader()));
ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader()).forEach(configurations::add);
Assert.notEmpty(configurations,
"No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}