掃二維碼與項(xiàng)目經(jīng)理溝通
我們?cè)谖⑿派?4小時(shí)期待你的聲音
解答本文疑問(wèn)/技術(shù)咨詢/運(yùn)營(yíng)咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流
自動(dòng)裝配是 SpringBoot 的核心功能,主要是讓開(kāi)發(fā)者盡可能少的關(guān)注一些基礎(chǔ)化的 Bean 的配置,實(shí)際上完成的工作是如何自動(dòng)將 Bean 裝載到 Ioc 容器中。

在 SpringBoot 中如果想要引入一個(gè)新的模塊,例如項(xiàng)目中想使用 Redis 緩存,只需要做以下幾步即可。
1、在 pom.xml 文件中引入 spring-boot-starter-data-redis 相關(guān)的 jar 包
org.springframework.boot
spring-boot-starter-data-redis
2、在 application.properties 文件中加入 Redis 相關(guān)的配置
spring.redis.host=127.0.0.1
spring.redis.port=63793、在代碼中引用 Redis 緩存的操作類(lèi)
@Autowired
private RedisTemplateredisTemplate; 為什么 RedisTemplate 可以被直接注入,它是什么時(shí)候加入到 Ioc 容器中的,這都是自動(dòng)裝配的功勞,我們一起來(lái)看一下。
SpringBoot 項(xiàng)目啟動(dòng)類(lèi)上有 @SpringBootApplication 這樣一個(gè)注解,它繼承了 @EnableAutoConfiguration,主要作用是幫助 Springboot 應(yīng)用把所有符合條件的配置類(lèi)都加載到當(dāng)前 SpringBoot 創(chuàng)建并使用的 Ioc 容器中。
這個(gè)注解主要由兩部分組成
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
...
}
@AutoConfigurationPackage 指定 SpringBoot 掃描的包范圍,主要邏輯在 AutoConfigurationPackages#register 方法中。
該方法有兩個(gè)參數(shù) registry 和 packageNames,在斷點(diǎn)中發(fā)現(xiàn) registry 實(shí)際上就是 DefaultListableBeanFactory 實(shí)例,packageNames 的值默認(rèn)是啟動(dòng)類(lèi)包所在的路徑,在這里將 @AutoConfigurationPackage 指定的包路徑添加到 DefaultListableBeanFactory,在后續(xù)Ioc容器掃描時(shí)將其加載進(jìn)去。
圖片
AutoConfigurationImportSelector 主要是實(shí)現(xiàn) importSelector 方法來(lái)實(shí)現(xiàn)基于動(dòng)態(tài) Bean 的加載功能,我們定位到 importSelector 方法看一下里面的邏輯。
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
//1、從配置文件spring-autoconfigure-metadata.properties中加載自動(dòng)裝配候選規(guī)則
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
//2、獲取@SpringBootApplication上配置的屬性值
AnnotationAttributes attributes = getAttributes(annotationMetadata);
//3、使用SpringFactoriesLoader 加載classpath路徑下META-INF\spring.factories中
//通過(guò)key=org.springframework.boot.autoconfigure.EnableAutoConfiguration獲取候選類(lèi)
List configurations = getCandidateConfigurations(annotationMetadata,attributes);
//4、去除重復(fù)值
configurations = removeDuplicates(configurations);
//5、獲取exclude屬性值,將exclude中的值排除掉
Set exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
//6、檢查候選配置類(lèi)上的注解@ConditionalOnClass,如果要求的類(lèi)不存在,則這個(gè)候選類(lèi)會(huì)被過(guò)濾不被加載
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
return StringUtils.toStringArray(configurations);
} 第一步和第三步邏輯中涉及到兩個(gè)非常重要的文件 spring-autoconfigure-metadata.properties、spring.factories
圖片
SPI ,全稱為 Service Provider Interface,是一種服務(wù)發(fā)現(xiàn)機(jī)制。它通過(guò)在 ClassPath 路徑下的 META-INF/services 文件夾查找文件,自動(dòng)加載文件里所定義的類(lèi)。這一機(jī)制為很多框架擴(kuò)展提供了可能,比如在 Dubbo、JDBC 中都使用到了 SPI 機(jī)制。
圖片
在 @EnableAutoConfiguration 分析中,兩種加載 Bean 到 Ioc 容器的方式,他們都是通過(guò) @import 引入,這里我們來(lái)分析一下 @import 是在哪里進(jìn)行加載的。
@Import(PersonConfig.class)
@Configuration
public class PersonConfiguration {
}
@Import(TestImportSelector.class)
@Configuration
public class ImportTestConfig {
}
public class TestImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"com.example.service.TestService"};
}
}
@Import(TestImportBeanDefinitorSelector.class)
@Configuration
public class ImportBeanDefinitionTestConfig {
}
public class TestImportBeanDefinitorSelector implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder
.rootBeanDefinition(Person.class)
.getBeanDefinition();
registry.registerBeanDefinition("person", beanDefinition);
}
}
@Override
public void refresh() throws BeansException, IllegalStateException {
//....省略n行代碼
//1.beanFactory后置處理邏輯,在這個(gè)方法里加載ConfigurationClassPostProcessor
invokeBeanFactoryPostProcessors(beanFactory);
//2.注冊(cè)bean后置處理邏輯
registerBeanPostProcessors(beanFactory);
//...省略n行代碼
//3.實(shí)例化非懶加載的bean,并加入到Ioc容器中
finishBeanFactoryInitialization(beanFactory);
//....省略n行代碼
}
@Nullable
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
throws IOException {
//...省略n行代碼
//加載@Import注解,遞歸解析,獲取導(dǎo)入的配置類(lèi)
processImports(configClass, sourceClass, getImports(sourceClass), true);
//...省略n行代碼
}
private void processImports(ConfigurationClass configClass,
SourceClass currentSourceClass,
Collection importCandidates,
boolean checkForCircularImports) {
//...省略n行代碼
if (candidate.isAssignable(ImportSelector.class)) {
//1.實(shí)現(xiàn)了ImportSelector接口的類(lèi)在@Import中引用邏輯
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 importSourceClasses = asSourceClasses(importClassNames);
processImports(configClass, currentSourceClass, importSourceClasses, false);
}
} else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
//2.實(shí)現(xiàn)了ImportBeanDefinitionRegistrar接口的類(lèi)在@Import中引用邏輯
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 {
//3.普通類(lèi)直接在@Import中引用邏輯
this.importStack.registerImport(
currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
processConfigurationClass(candidate.asConfigClass(configClass));
}
//...省略n行代碼
} 總結(jié)一下就是如下的方法鏈調(diào)用
refresh()=>invokeBeanFactoryPostProcessors()=>postProcessBeanDefinitionRegistry()=>parse()=>
doProcessConfigurationClass()=>processImports()
前面我們分析了自動(dòng)裝配的主要邏輯,那么 SpringBoot 啟動(dòng)類(lèi)又是如何加入到Ioc容器中的呢?
private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
//...省略n行代碼
//加載啟動(dòng)類(lèi),將啟動(dòng)類(lèi)注入到Ioc容器中
load(context, sources.toArray(new Object[0]));
//...省略n行代碼
}
圖片
總結(jié)一下就是如下的方法鏈調(diào)用
run()=>prepareContext()()=>load()=>parse()=>register()
基于以上3塊的分析我們可以得到如下一個(gè)關(guān)于自動(dòng)裝配的流程圖
圖片
學(xué)習(xí)源碼的過(guò)程中如果不了解源碼的整體思路,直接看代碼會(huì)迷失在源碼的海洋中。要了解代碼的整體脈絡(luò),以總-分-總的方式去學(xué)習(xí),學(xué)會(huì)舍棄部分無(wú)關(guān)的代碼,才能高效的閱讀和學(xué)習(xí)源碼,從中汲取到代碼的精華所在,提升自己的編程能力。

我們?cè)谖⑿派?4小時(shí)期待你的聲音
解答本文疑問(wèn)/技術(shù)咨詢/運(yùn)營(yíng)咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流