springboot的擴(kuò)展解耦,仿照java的SPI機(jī)制飘蚯,使用SpringFactoriesLoader加載spring.factories文件實(shí)現(xiàn)兄朋。
java SPI(Service Provider Inteface)
java中為廠商或插件設(shè)計(jì)的擴(kuò)展機(jī)制,為什么引入脂倦?
- 系統(tǒng)中抽象的各個(gè)模塊畅厢,比如日志模塊冯痢,xml解析模塊、jdbc模塊等框杜,每個(gè)模塊有多種實(shí)現(xiàn)方案浦楣。
- 面向?qū)ο蟪绦蛟O(shè)計(jì)中,一般推薦模塊間基于接口編程咪辱,模塊間不對(duì)實(shí)現(xiàn)類進(jìn)行硬編碼振劳。一旦代碼中涉及具體的實(shí)現(xiàn)類,就違反了可拔插的原則油狂。
- 如果需要替換一種實(shí)現(xiàn)历恐,就需要修改代碼寸癌。為了實(shí)現(xiàn)在模塊裝配時(shí),能不在程序里動(dòng)態(tài)指明弱贼,就需要一種服務(wù)發(fā)現(xiàn)機(jī)制蒸苇。
解決方案
- java SPI, 為某個(gè)接口尋找服務(wù)實(shí)現(xiàn)的機(jī)制吮旅,類似IOC思想溪烤,將裝配的控制權(quán)移到程序之外,在模塊化設(shè)計(jì)中該機(jī)制尤其重要
也即 Java提供的SPI接口和調(diào)用方在java的核心類庫(kù)庇勃,接口的具體實(shí)現(xiàn)類由廠商或插件設(shè)計(jì)開發(fā)檬嘀,
java虛擬機(jī)中采用雙親委派模型進(jìn)行類的加載,而java SPI實(shí)現(xiàn)類的加載责嚷,不適用雙親委派模型鸳兽,因而有了破壞雙親委派模型的說(shuō)法。為什么說(shuō) Java SPI 的設(shè)計(jì)違反雙親委派原則
雖然用了“破壞”這個(gè)詞再层,并不帶有貶義贸铜,只要有足夠意義和理由,突破已有的原則就可認(rèn)為是創(chuàng)新聂受。秉持這個(gè)觀點(diǎn),再來(lái)看spring 私有框架的擴(kuò)展類加載過(guò)程烤镐,并不符合傳統(tǒng)的雙親委派模型的類加載蛋济,仍值得學(xué)習(xí),弄懂其實(shí)現(xiàn)炮叶,就可掌握在spring基礎(chǔ)上擴(kuò)展的各類組件及工具集碗旅。諸如spring boot、spring cloud 等镜悉。
spring SPI擴(kuò)展機(jī)制
類似Java SPI擴(kuò)展加載機(jī)制祟辟。在META-INF/spring.factories文件中配置接口的實(shí)現(xiàn)類名稱,程序中讀取這些配置文件并實(shí)例化侣肄。這種自定義的SPI機(jī)制是Spring Boot Starter實(shí)現(xiàn)的基礎(chǔ)旧困。
在開始介紹SpringFactoriesLoader加載spring.factories文件前,先了解java類加載器加載資源的過(guò)程
java的類加載器除加載 class 外稼锅,還有一個(gè)重要功能就是加載資源吼具,從 jar 包中讀取任何資源文件,如ClassLoader.getResources(Stringname) 方法讀取 jar 包中的資源文件矩距,代碼如下:
public URL getResource(String name) {
URL url;
if (parent != null) {
url = parent.getResource(name);
} else {
url = getBootstrapResource(name);
}
if (url == null) {
url = findResource(name);
}
return url;
}
加載資源的過(guò)程拗盒,與雙親委派模型類加載的過(guò)程一樣,首先判斷父類是否為空锥债,不為空則將任務(wù)委派給父類加載器執(zhí)行資源加載陡蝇,直到啟動(dòng)類加載器BootstrapClassLoader痊臭。最后才輪到自己查找,而不同的類加載器負(fù)責(zé)掃描不同路徑下的 jar 包登夫,就如同加載 class 一樣趣兄,最后會(huì)掃描所有的 jar 包,找到符合條件的資源文件悼嫉。
類加載器的 ClassLoader.findResources(name) 方法會(huì)遍歷其負(fù)責(zé)加載的所有 jar 包艇潭,找到 jar 包中名稱為 name 的資源文件,這里的資源可以是任何文件戏蔑,甚至是 .class 文件蹋凝,比如下面的示例,用于查找 ConcurrentHashMap.class 文件:
public static void main(String[] args) throws IOException {
String name = "java/util/concurrent/ConcurrentHashMap.class";
Enumeration<URL> urls = Thread.currentThread()
.getContextClassLoader().getResources(name);
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
System.out.println(url.toString());
}
}
運(yùn)行的結(jié)果
jar:file:/C:/Program%20Files/Java/jdk1.8.0_25/jre/lib/rt.jar!/java/util/concurrent/ConcurrentHashMap.class
有了ClassLoder加載資源文件的知識(shí)总棵,接下來(lái)了解SpringFactoriesLoader加載spring.factories文件過(guò)程
spring-core包中定義了SpringFactoriesLoader類鳍寂,該類定義兩個(gè)對(duì)外方法
- loadFactories 根據(jù)接口類獲取其實(shí)現(xiàn)類的實(shí)例,返回對(duì)象列表
- loadFactoryNames 根據(jù)接口獲取其接口類的名稱情龄,返回類名列表
loadFactoryNames()源碼實(shí)現(xiàn):
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
//spring.factories文件的格式為:key=value1,value2,value3
//從所有jar文件中找到MET-INF/spring.factories文件
//然后從文件中解析初key=factoryClass類名稱的所有value值
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
try {
//取得資源文件的URL
Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
List<String> result = new ArrayList<String>();
//遍歷所有的URL
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
//根據(jù)資源文件的url解析properties
Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
String factoryClassNames = properties.getProperty(factoryClassName);
//組裝數(shù)據(jù)并返回
result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
}
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() +
"] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
從 ClassPath 下的每個(gè) Jar 包中搜尋所有 META-INF/spring.factories 配置文件迄汛,然后解析 properties 文件,找到指定名稱的配置后返回骤视。
注意鞍爱,不僅在ClassPath 路徑下查找,也會(huì)掃描所有路徑下的 Jar 包专酗,只不過(guò)spring.factories只存在于 Classpath 下的 jar 包中睹逃。spring.factories 示例:
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration
...
執(zhí)行 loadFactoryNames(EnableAutoConfiguration.class,classLoader) ,得到對(duì)應(yīng)的一組 @Configuration 類祷肯,通過(guò)反射實(shí)例化這些類注入到 IOC 容器中沉填,從而容器中擁有了一系列標(biāo)注 @Configuration的JavaConfig 形式的配置類。
SpringFactoriesLoader本質(zhì)上屬于 Spring 框架私有的一種擴(kuò)展方案佑笋,類似于 SPI翼闹,即不采用雙親委派模型加載類,Spring Boot 在 Spring 基礎(chǔ)上擴(kuò)展的很多核心功能都是基于此機(jī)制實(shí)現(xiàn)
spring.factories文件加載過(guò)程詳解
1蒋纬、啟動(dòng)類入口
2猎荠、springboot使用啟動(dòng)類注解@SpringBootApplication
3、進(jìn)入@EnableAutoConfiguration颠锉,通過(guò)@Import加載EnableAutoConfigurationImportSelector類法牲, @import作用是:spring IOC容器中沒有注入EnableAutoConfigurationImportSelector類,但springboot啟動(dòng)需要用到琼掠,因此通過(guò)@Import注解將該類注入到spring容器
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
@SpringBootConfiguration
@EnableAutoConfiguration
public @interface SpringBootApplication {}
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {}
springboot通過(guò)@SpringBootApplication注解拒垃,在spring的基礎(chǔ)上進(jìn)行功能擴(kuò)展。引入其他業(yè)務(wù)組建的平臺(tái)邏輯(擴(kuò)展操作)瓷蛙,在@Import(EnableAutoConfigurationImportSelector.class)中實(shí)現(xiàn)悼瓮,spring cloud config/ spring cloud eureka等組件均通過(guò)該方式構(gòu)建
EnableAutoConfigurationImportSelector為ImportSelector實(shí)現(xiàn)類戈毒,所有ImportSelector接口實(shí)現(xiàn)類,會(huì)在spring容器啟動(dòng)時(shí)被ConfigurationClassParser.processImports()實(shí)例化横堡,并執(zhí)行selectImports方法埋市。 springboot啟動(dòng),是先進(jìn)行注解@SpringBootApplication的掃描命贴,還是先運(yùn)行SpringApplication.run(Application.class)道宅,由上述分析可知,注解掃描優(yōu)先
4胸蛛、EnableAutoConfigurationImportSelector類
@Deprecated
public class EnableAutoConfigurationImportSelector
extends AutoConfigurationImportSelector {
@Import和xml配置的 <import />標(biāo)簽作用一樣污茵,允許通過(guò)它引入 @Configuration 注解的類 (java config), 引入ImportSelector接口(要通過(guò)它去判定要引入哪些@Configuration) 和 ImportBeanDefinitionRegistrar 接口的實(shí)現(xiàn)葬项, 也包括 @Component注解的普通類泞当。但是如果要引入另一個(gè)xml 文件形式配置的 bean, 則需要通過(guò) @ImportResource 注解。
EnableAutoConfigurationImportSelector繼承AutoConfigurationImportSelector (繼承接口ImportSelector)類民珍,并覆蓋其isEnabled方法襟士,后續(xù)實(shí)例化EnableAutoConfigurationImportSelector時(shí),調(diào)用的isEnabled方法嚷量,取自覆蓋后的方法陋桂。
ImportSelector接口
@Import 實(shí)現(xiàn),通常要借助 ImportSelector 接口的實(shí)現(xiàn)類決定引入哪些 @Configuration津肛。 如果ImportSelector實(shí)現(xiàn)類章喉,實(shí)現(xiàn)了以下四個(gè)Aware 接口的一個(gè)或多個(gè)(EnvironmentAware、BeanFactoryAware身坐、BeanClassLoaderAware、ResourceLoaderAware)落包, 在bean生命周期的初始化階段部蛇, 會(huì)先回調(diào)aware接口方法的實(shí)現(xiàn),織入系統(tǒng)變量咐蝇,使得實(shí)例化bean擁有操作系統(tǒng)變量的能力涯鲁。
/**
* Interface to be implemented by types that determine which @{@link Configuration}
* class(es) should be imported based on a given selection criteria, usually one or more
* annotation attributes.
*
* An {@link ImportSelector} may implement any of the following
* {@link org.springframework.beans.factory.Aware Aware} interfaces, and their respective
* methods will be called prior to {@link #selectImports}:
* {@link org.springframework.context.EnvironmentAware EnvironmentAware}
* {@link org.springframework.beans.factory.BeanFactoryAware BeanFactoryAware}
* {@link org.springframework.beans.factory.BeanClassLoaderAware BeanClassLoaderAware}
* {@link org.springframework.context.ResourceLoaderAware ResourceLoaderAware}
* */
public interface ImportSelector {
/**
* Select and return the names of which class(es) should be imported based on
* the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
*/
String[] selectImports(AnnotationMetadata importingClassMetadata);
}
5、AutoConfigurationImportSelector.selectImports()方法
@Override
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);
}
}
為了選出想要加載的import類有序,如何獲取呢抹腿?其實(shí)是通過(guò)SpringFactoriesLoader去加載對(duì)應(yīng)的spring.factories
下面展示如何和此類建立關(guān)系。
進(jìn)入selectImports() -> getCandidateConfigurations()
/**
* Return the auto-configuration class names that should be considered. By default
* this method will load candidates using {@link SpringFactoriesLoader} with
* {@link #getSpringFactoriesLoaderFactoryClass()}.
* @param metadata the source metadata
* @param attributes the {@link #getAttributes(AnnotationMetadata) annotation
* attributes}
* @return a list of candidate configurations
*/
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;
}
/**
* Return the class used by {@link SpringFactoriesLoader} to load configuration
* 返回SpringFactoriesLoader類加載配置需要的接口類
* candidates.
* @return the factory class
*/
protected Class<?> getSpringFactoriesLoaderFactoryClass() {
return EnableAutoConfiguration.class;
}
注釋中的描述旭寿,使用SpringFactoriesLoader類警绩、AutoConfigurationImportSelector.getSpringFactoriesLoaderFactoryClass()組合,加載指定資源盅称。
SpringFactoriesLoader.loadFactoryNames所需的參數(shù)肩祥,其中一個(gè)參數(shù)通過(guò)調(diào)用getSpringFactoriesLoaderFactoryClass()后室,獲取返回值EnableAutoConfiguration.class ,
接下來(lái)SpringFactoriesLoader根據(jù)這個(gè)interface混狠,查找所有spring.factories中EnableAutoConfiguration.class對(duì)應(yīng)的values岸霹,并返回。
SpringFactoriesLoader如何加載
loadFactoryNames方法
/**
* Load the fully qualified class names of factory implementations of the
* given type from {@value #FACTORIES_RESOURCE_LOCATION}, using the given
* class loader.
* @param factoryClass the interface or abstract class representing the factory
* @param classLoader the ClassLoader to use for loading resources; can be
* {@code null} to use the default
* @see #loadFactories
* @throws IllegalArgumentException if an error occurs while loading factory names
*/
//factoryClass傳入的參數(shù)值為EnableAutoConfiguration.class
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
String factoryClassName = factoryClass.getName(); //factoryClassName的取值為“EnableAutoConfiguration"
try {
//FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"
//urls為查找到的spring.factories文件列表
Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
List<String> result = new ArrayList<String>();
while (urls.hasMoreElements()) {
//遍歷spring.factories資源文件
URL url = urls.nextElement();
//解析文件中的屬性值将饺,存入Properties
Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
//根據(jù)鍵EnableAutoConfiguration找到配置文件中對(duì)應(yīng)的屬性值
String factoryClassNames = properties.getProperty(factoryClassName);
result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames))); //將取到的values按逗號(hào)分隔贡避,并轉(zhuǎn)換成list
}
return result; //返回取值list
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() +
"] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
返回值為list,取值從org.springframework.boot.autoconfigure.EnableAutoConfiguration的value={‘org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration’予弧,‘org.springframework.boot.autoconfigure.aop.AopAutoConfiguration’刮吧,‘org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration’,桌肴,皇筛,,坠七,水醋,}
Springboot 對(duì)@Import注解的處理過(guò)程
入口
AbstractApplicationContext.refresh()
-> AbstractApplicationContext.invokeBeanFactoryPostProcessors(beanFactory)
->PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors)
->ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)
-> ConfigurationClassPostProcessor.processConfigBeanDefinitions(BeanDefinitionRegistry registry)
- springboot初始化的普通context(非web) 是AnnotationConfigApplicationContext(spring注解容器)
- 在初始化的時(shí)候會(huì)初始化兩個(gè)工具類, AnnotatedBeanDefinitionReader 和 ClassPathBeanDefinitionScanner 分別用來(lái)從 annotation driven 的配置和xml的配置中讀取beanDefinition并向context注冊(cè),
- 那么在初始化 AnnotatedBeanDefinitionReader 的時(shí)候彪置, 會(huì)向BeanFactory注冊(cè)一個(gè)ConfigurationClassPostProcessor 用來(lái)處理所有的基于annotation的bean, 這個(gè)ConfigurationClassPostProcessor 是 BeanFactoryPostProcessor 的一個(gè)實(shí)現(xiàn)拄踪,springboot會(huì)保證在 invokeBeanFactoryPostProcessors(beanFactory) 方法中調(diào)用注冊(cè)到它上邊的所有的BeanFactoryPostProcessor
- 因此,在spring容器啟動(dòng)時(shí)拳魁,會(huì)調(diào)用ConfigurationClassPostProcessor .postProcessBeanDefinitionRegistry()方法惶桐。
ConfigurationClassParser
在ConfigurationClassPostProcessor .postProcessBeanDefinitionRegistry()方法中實(shí)例化ConfigurationClassParser調(diào)用
// Parse each @Configuration class
ConfigurationClassParser parser = new ConfigurationClassParser(
this.metadataReaderFactory, this.problemReporter, this.environment,
this.resourceLoader, this.componentScanBeanNameGenerator, registry);
在 ConfigurationClassParser -> processConfigurationClass() -> doProcessConfigurationClass() 方法中找到( 分別按次序處理@PropertySource, @ComponentScan, @Import, @ImportResource, 在處理這些注解時(shí)潘懊,通過(guò)遞歸保證所有的都被處理)
取重點(diǎn)代碼段
// Process any @Import annotations
processImports(configClass, sourceClass, getImports(sourceClass), true);
processImports流程如下:
- 首先姚糊,如果import 是 ImportSelector.class 接口的實(shí)現(xiàn), 則初始化被Import的類授舟, 再調(diào)用它的selectImports方法獲得引入的configuration救恨, 遞歸處理
- 其次,如果import是 ImportBeanDefinitionRegistrar 接口的實(shí)現(xiàn)释树, 則初始化后將對(duì)當(dāng)前對(duì)象的處理委托給這個(gè)ImportBeanDefinitionRegistrar (不是特別明白肠槽, 只是我的猜測(cè))
- 最后, 將import引入的類作為一個(gè)正常的類來(lái)處理 ( 調(diào)用最外層的doProcessConfigurationClass())
綜上奢啥, 如果引入正常的component秸仙, 會(huì)作為@Component 或 @Configuration處理, 在BeanFactory中通過(guò)getBean()獲取桩盲, 但如果是 ImportSelector 或ImportBeanDefinitionRegistrar 接口的實(shí)現(xiàn)寂纪, spring不會(huì)將它們注冊(cè)到beanFactory中,而只是調(diào)用它們的方法正驻。
回顧本文之前所講springboot啟動(dòng)弊攘,@SpringBootApplication注解通過(guò)@Import注入到spring容器的EnableAutoConfigurationImportSelector類抢腐,其繼承的父類方法AutoConfigurationImportSelector.selectImports()在此處被調(diào)用。
processImports實(shí)現(xiàn)代碼塊如下
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
Collection<SourceClass> importCandidates, boolean checkForCircularImports) throws IOException {
襟交,迈倍,,捣域,
for (SourceClass candidate : importCandidates) {
if (candidate.isAssignable(ImportSelector.class)) {
// Candidate class is an ImportSelector -> delegate to it to determine imports
Class<?> candidateClass = candidate.loadClass();
ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
ParserStrategyUtils.invokeAwareMethods(
selector, this.environment, this.resourceLoader, this.registry);
if (this.deferredImportSelectors != null && selector instanceof DeferredImportSelector) {
this.deferredImportSelectors.add(
new DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector));
}
else {
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
processImports(configClass, currentSourceClass, importSourceClasses, false);
}
}
else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
// Candidate class is an ImportBeanDefinitionRegistrar ->
// delegate to it to register additional bean definitions
Class<?> candidateClass = candidate.loadClass();
ImportBeanDefinitionRegistrar registrar =
BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);
ParserStrategyUtils.invokeAwareMethods(
registrar, this.environment, this.resourceLoader, this.registry);
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
}
else {
// Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
// process it as an @Configuration class
this.importStack.registerImport(
currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
processConfigurationClass(candidate.asConfigClass(configClass));
}
}
啼染,,焕梅,迹鹅,
}
}