剛作後端開發的時候,最先接觸的是基礎的spring,爲了引用二方包提供bean,還須要在xml中增長對應的包<context:component-scan base-package="xxx" />
或者增長註解@ComponentScan({ "xxx"})
。當時以爲挺urgly的,但也沒有去研究有沒有更好的方式。java
直到接觸Spring Boot 後,發現其能夠自動引入二方包的bean。不過一直沒有看這塊的實現原理。直到最近面試的時候被問到。因此就看了下實現邏輯。git
更多文章見我的博客:github.com/farmerjohng…github
講原理前先說下使用姿式。面試
在project A中定義一個bean。spring
package com.wangzhi;
import org.springframework.stereotype.Service;
@Service
public class Dog {
}
複製代碼
並在該project的resources/META-INF/
下建立一個叫spring.factories
的文件,該文件內容以下後端
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.wangzhi.Dog
複製代碼
而後在project B中引用project A的jar包。springboot
projectA代碼以下:bash
package com.wangzhi.springbootdemo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;
@EnableAutoConfiguration
public class SpringBootDemoApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(SpringBootDemoApplication.class, args);
System.out.println(context.getBean(com.wangzhi.Dog.class));
}
}
複製代碼
打印結果:this
com.wangzhi.Dog@3148f668
複製代碼
整體分爲兩個部分:一是收集全部spring.factories
中EnableAutoConfiguration
相關bean的類,二是將獲得的類註冊到spring容器中。url
在spring容器啓動時,會調用到AutoConfigurationImportSelector#getAutoConfigurationEntry
protected AutoConfigurationEntry getAutoConfigurationEntry( AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
// EnableAutoConfiguration註解的屬性:exclude,excludeName等
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 獲得全部的Configurations
List<String> configurations = getCandidateConfigurations(annotationMetadata,
attributes);
// 去重
configurations = removeDuplicates(configurations);
// 刪除掉exclude中指定的類
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
複製代碼
getCandidateConfigurations
會調用到方法loadFactoryNames
:
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
// factoryClassName爲org.springframework.boot.autoconfigure.EnableAutoConfiguration
String factoryClassName = factoryClass.getName();
// 該方法返回的是全部spring.factories文件中key爲org.springframework.boot.autoconfigure.EnableAutoConfiguration的類路徑
return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
// 找到全部的"META-INF/spring.factories"
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
// 讀取文件內容,properties相似於HashMap,包含了屬性的key和value
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryClassName = ((String) entry.getKey()).trim();
// 屬性文件中能夠用','分割多個value
for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryClassName, factoryName.trim());
}
}
}
cache.put(classLoader, result);
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
複製代碼
在上面的流程中獲得了全部在spring.factories
中指定的bean的類路徑,在processGroupImports
方法中會以處理@Import註解同樣的邏輯將其導入進容器。
public void processGroupImports() {
for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
// getImports即上面獲得的全部類路徑的封裝
grouping.getImports().forEach(entry -> {
ConfigurationClass configurationClass = this.configurationClasses.get(
entry.getMetadata());
try {
// 和處理@Import註解同樣
processImports(configurationClass, asSourceClass(configurationClass),
asSourceClasses(entry.getImportClassName()), false);
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to process import candidates for configuration class [" +
configurationClass.getMetadata().getClassName() + "]", ex);
}
});
}
}
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass, Collection<SourceClass> importCandidates, boolean checkForCircularImports) {
...
// 遍歷收集到的類路徑
for (SourceClass candidate : importCandidates) {
...
//若是candidate是ImportSelector或ImportBeanDefinitionRegistrar類型其處理邏輯會不同,這裏不關注
// Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
// process it as an @Configuration class
this.importStack.registerImport(
currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
// 看成 @Configuration 處理
processConfigurationClass(candidate.asConfigClass(configClass));
...
}
...
}
複製代碼
能夠看到,在第一步收集的bean類定義,最終會被以Configuration
同樣的處理方式註冊到容器中。
@EnableAutoConfiguration
註解簡化了導入了二方包bean的成本。提供一個二方包給其餘應用使用,只須要在二方包裏將對外暴露的bean定義在spring.factories
中就行了。對於不須要的bean,能夠在使用方用@EnableAutoConfiguration
的exclude
屬性進行排除。