参考文章: spring.factories
先说下原理:spring.factories是spring-boot的SPI机制,采用key=value键值对的形式保存。springBoot拓展了java spi,不仅仅支持接口,还支持注解。比如:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.xxx.xxx.xxx.autocfg.xxxDataSourceConfiguration
EnableAutoConfiguration就是注解。
Spring在启动的时候,会在不同生命周期的地方调用SpringFactoriesLoader . loadFactoryNames。然后在不同生命周期的地方,匹配出特定的接口或者注解,然后进行处理。(不同注解或者接口,作用于不同的生命周期。)
参考:BeanDefinitionRegistryPostProcessor -> BeanFactoryPostProcessor -> InstantiationAwareBeanPostProcessor -> BeanPostProcessor
当然我这只列出来四个spring最常用的扩展点接口。其实还有其他扩展点接口,但是由于不常用就不列出了,只需要知道它们会被spring安排的明明白白,只会出现在它们该出现的地方。我们程序员能做的仅仅是实现这些拓展点,并不能改变拓展点被执行的位置(即不能改变拓展点的生命周期位置)。
什么还不知道拓展点是啥?
拓展点就是spring为我们开发人员预留好的接口,或者注解等等。
比如:
1、org.springframework.beans.factory.config.BeanPostProcessor#postProcessBeforeInitialization,这个就是拓展点。作用于bean实例化后,初始化前的生命周期
2、org.springframework.beans.factory.config.BeanPostProcessor#postProcessAfterInitialization。该扩展点,作用于bean执行完afterPropertiesSet和init-method之后的生命周期。
下面上源码:
比如:在spring启动的时候,会调用invokeBeanFactoryPostProcessors核心方法。该方法里面就拓展了加载spring-factories的地方。
具体方法在org.springframework.context.annotation.ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry里面。如下:
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
int registryId = System.identityHashCode(registry);
if (this.registriesPostProcessed.contains(registryId)) {
throw new IllegalStateException(
"postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
}
if (this.factoriesPostProcessed.contains(registryId)) {
throw new IllegalStateException(
"postProcessBeanFactory already called on this post-processor against " + registry);
}
this.registriesPostProcessed.add(registryId);
processConfigBeanDefinitions(registry);
}
继续追踪到org.springframework.context.annotation.ConfigurationClassParser#parse方法,如下:
public void parse(Set<BeanDefinitionHolder> configCandidates) {
this.deferredImportSelectors = new LinkedList<DeferredImportSelectorHolder>();
for (BeanDefinitionHolder holder : configCandidates) {
BeanDefinition bd = holder.getBeanDefinition();
try {
if (bd instanceof AnnotatedBeanDefinition) {
parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
}
else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
}
else {
parse(bd.getBeanClassName(), holder.getBeanName());
}
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
}
}
processDeferredImportSelectors();
}
核心就在processDeferredImportSelectors方法。
private void processDeferredImportSelectors() {
List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
this.deferredImportSelectors = null;
Collections.sort(deferredImports, DEFERRED_IMPORT_COMPARATOR);
for (DeferredImportSelectorHolder deferredImport : deferredImports) {
ConfigurationClass configClass = deferredImport.getConfigurationClass();
try {
String[] imports = deferredImport.getImportSelector().selectImports(configClass.getMetadata());
processImports(configClass, asSourceClass(configClass), asSourceClasses(imports), false);
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to process import candidates for configuration class [" +
configClass.getMetadata().getClassName() + "]", ex);
}
}
}
1、DeferredImportSelector 是 ImportSelector 的一个变种。
2、 ImportSelector 被设计成其实和@Import注解的类同样的导入效果,但是实现 ImportSelector的类可以条件性地决定导入哪些配置。
3、DeferredImportSelector 的设计目的是在所有其他的配置类被处理后才处理。这也正是该语句被放到本函数最后一行的原因。
该方法有很重要的一句代码:
String[] imports = deferredImport.getImportSelector().selectImports(configClass.getMetadata());
select出所有deferredImport类,即延迟import类。
进入方法org.springframework.boot.autoconfigure.AutoConfigurationImportSelector#selectImports中:
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
try {
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
AnnotationAttributes attributes = getAttributes(annotationMetadata);
//重点看!!!!!
List<String> configurations = getCandidateConfigurations(annotationMetadata,
attributes);
configurations = removeDuplicates(configurations);
configurations = sort(configurations, autoConfigurationMetadata);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
return configurations.toArray(new String[configurations.size()]);
}
catch (IOException ex) {
throw new IllegalStateException(ex);
}
}
看下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;
}
是不是找到地方了,loadFactoryNames出现了。参数是:EnableAutoConfiguration,classLoader。这句话的意思就是:从所有spring-factoies加载的key-value对中,找出EnableAutoConfiguration注解的所有实现类。不管这个实现类,是在哪个jar包下,都会被加载到。如果我们想要把自己的jar打给别人,然后又想在人家应用启动的时候加载我们自己bean,那么就可以在jar包里面添加spring-factories文件,添加一个key-value:org.springframework.boot.autoconfigure.EnableAutoConfiguration=xxxx。类似文章开始那样。看下我们工程配置,如下:
image.png通过这种方式,我们就可以在应用启动时,自动加载二方包配置的bean,无需本应用开发者配置任何代码。如果是以前,可能需要写个config类,自己手动配置很多二方包的bean。现在只要对方把spring-factoies加入它们的jar包,然后所以依赖该包的应用,都无需显示配置了。
总结:
- spring-factories是spring给开发人员提供的SPI
- spring在启动过程中的整个生命周期,会在不同位置调用不同拓展点。而这些拓展点是开发人员实现的,为了让spring能够动态加载这些拓展点,需要SPI能力
- 想加载二方包里面的bean,但是又不想配置。那么二方包里面的spring-factories能够满足你。它里面的自动配置类,就相当于你自己工程的xxxApplication类一样。
网友评论