1. @Import
注解在springBoot中間接的廣泛應用
@Import
在springboot中并沒有直接顯式的使用
@Import
标簽,而是通過
@Import
标簽來間接的提供了很多自動配置的注解。比如
@EnableAutoConfiguration
,
@EnableConfigurationProperties
等。這些标簽的實作都是通過使用
@Import
标簽來完成的。
......
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
......
}
......
@Import(EnableConfigurationPropertiesRegistrar.class)
public @interface EnableConfigurationProperties {
......
}
可以發現都是通過
@Import
來完成的。
2.spring中的 @Import
注解
@Import
2.1 @Import
注解的作用
@Import
@Import
标簽可以導入一個或者多個元件類,通常是
@Configuration
注入的bean。提供了與xml中
<import/>
标簽類型的功能,能夠導入
@Configuration
類。
public @interface Import {
/**
* {@link Configuration @Configuration}, {@link ImportSelector},
* {@link ImportBeanDefinitionRegistrar}, or regular component classes to import.
*/
Class<?>[] value();
}
導入的類可以是
@Configuration
類型的配置類,實作了
ImportSelector
接口的類,實作了
ImportBeanDefinitionRegistrar
的類或者正常的元件類。
2.2 @Import
的解析前的處理
@Import
@Import
處理的位置其實跟
@Conditional
注解都在同一個類中,處理的時機也是一樣的。這裡可以去看看
@Conditional
注解解析的邏輯。
2.2.1 容器的重新整理時候的準備
在容器重新整理方法就定義在
AbstractApplicationContext
類的
refresh
方法中。在這個裡面會有解析注冊以及執行個體話bean和其他的步驟,我們要看的就是解析跟注冊步驟。
public void refresh() throws BeansException, IllegalStateException {
......
invokeBeanFactoryPostProcessors(beanFactory);
}
在
invokeBeanFactoryPostProcessors
方法中會執行個體化所有的
BeanFactoryPostProcessor
類型的類并調用實作的
postProcessBeanFactory
方法。
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
......
}
2.2.2
PostProcessorRegistrationDelegate
處理
BeanDefinitionRegistry
以及
BeanFactoryPostProcessor
這裡直接進入到
invokeBeanFactoryPostProcessors
方法。會有兩種處理方式:
- 目前的
是beanFactory
BeanDefinitionRegistry
類型的
(1) 先處理
,然後按照是否實作了beanFactory
,PriorityOrdered
以及兩個都沒有實作的順序來處理Ordered
BeanDefinitionRegistryPostProcessor
接口的實作類。
(2)處理實作了
接口的子類依次調用實作的BeanFactoryPostProcessor
方法postProcessBeanFactory
- 目前的
不是beanFactory
類型的,則直接處理實作了BeanDefinitionRegistry
接口的子類依次調用實作的BeanFactoryPostProcessor
方法postProcessBeanFactory
其中會處理
@Import
标簽的
ConfigurationClassPostProcessor
實作類
BeanDefinitionRegistryPostProcessor
接口跟
PriorityOrdered
接口間接實作了
BeanFactoryPostProcessor
接口是以無論怎麼樣都會被調用的。
public static void invokeBeanFactoryPostProcessors(
ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {
//如果目前的beanFactory是BeanDefinitionRegistry的,則需要将已經存在的beanDefinition進行注冊
if (beanFactory instanceof BeanDefinitionRegistry) {
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
List<BeanFactoryPostProcessor> regularPostProcessors = new ArrayList<>();
//儲存注冊bean的BeanDefinitionRegistryPostProcessor
List<BeanDefinitionRegistryPostProcessor> registryProcessors = new ArrayList<>();
//疊代beanFactoryPostProcessors如果是BeanDefinitionRegistryPostProcessor子類則加入到registryProcessors集合中
for (BeanFactoryPostProcessor postProcessor : beanFactoryPostProcessors) {
if (postProcessor instanceof BeanDefinitionRegistryPostProcessor) {
BeanDefinitionRegistryPostProcessor registryProcessor =
(BeanDefinitionRegistryPostProcessor) postProcessor;
//處理beanFactory中的beanDefinitionNames
registryProcessor.postProcessBeanDefinitionRegistry(registry);
registryProcessors.add(registryProcessor);
}
else {
regularPostProcessors.add(postProcessor);
}
}
List<BeanDefinitionRegistryPostProcessor> currentRegistryProcessors = new ArrayList<>();
//先處理同時實作了PriorityOrdered接口的BeanDefinitionRegistryPostProcessor的實作類
String[] postProcessorNames =
beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
for (String ppName : postProcessorNames) {
//如果目前的BeanDefinitionRegistryPostProcessor類的實作類也實作了PriorityOrdered類,則加入當目前需要注冊的BeanDefinitionRegistryPostProcessor集合
if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
processedBeans.add(ppName);
}
}
//進行排序
sortPostProcessors(currentRegistryProcessors, beanFactory);
//加入到registryProcessors集合
registryProcessors.addAll(currentRegistryProcessors);
//調用實作了BeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry方法
invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
currentRegistryProcessors.clear();
......
invokeBeanFactoryPostProcessors(registryProcessors, beanFactory);
invokeBeanFactoryPostProcessors(regularPostProcessors, beanFactory);
}else {
// Invoke factory processors registered with the context instance.
invokeBeanFactoryPostProcessors(beanFactoryPostProcessors, beanFactory);
}
}
2.2.3
ConfigurationClassPostProcessor
的
processConfigBeanDefinitions
跟
processConfigBeanDefinitions
解析bean
上面提到了
ConfigurationClassPostProcessor
無論怎麼樣都會被調用。這裡先看看實作的
postProcessBeanDefinitionRegistry
方法跟
postProcessBeanFactory
的共同點。
//在refresh方法中最先被調用
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
//給目前的BeanDefinitionRegistry類型的beanFactory設定一個全局hash值,避免重複處理
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);
}
//加入到已經處理的PostProcessed集合中
this.registriesPostProcessed.add(registryId);
//進行處理
processConfigBeanDefinitions(registry);
}
/**
* Prepare the Configuration classes for servicing bean requests at runtime by replacing them with CGLIB-enhanced subclasses.
*/
//在refresh方法中第二被調用
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
//給目前的ConfigurableListableBeanFactory類型的beanFactory設定一個全局hash值,避免重複處理
int factoryId = System.identityHashCode(beanFactory);
if (this.factoriesPostProcessed.contains(factoryId)) {
throw new IllegalStateException(
"postProcessBeanFactory already called on this post-processor against " + beanFactory);
}
//加入到已經處理的集合中
this.factoriesPostProcessed.add(factoryId);
//如果目前的beanFactory不在registriesPostProcessed中則進行處理
if (!this.registriesPostProcessed.contains(factoryId)) {
// BeanDefinitionRegistryPostProcessor hook apparently not supported...
// Simply call processConfigurationClasses lazily at this point then.
processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);
}
//将beanFactory中用Configuration注解配置的bean進行動态加強
enhanceConfigurationClasses(beanFactory);
//增加一個ImportAwareBeanPostProcessor
beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
}
發現一個共同點就是,如果沒有解析過的
beanFactory
進來都會調用
processConfigBeanDefinitions
方法來處理
Configuration
類。在這個方法中有兩個解析的位置。
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
//循環處理知道所有的bean都處理完,包含bean内部定義的bean
do {
//第一個解析的位置,這裡是解析能夠直接擷取的候選配置bean。可能是Component,ComponentScan,Import,ImportResource或者有Bean注解的bean
parser.parse(candidates);
parser.validate();
//擷取上面封裝已經解析過的配置bean的ConfigurationClass集合
Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
//移除前面已經處理過的
configClasses.removeAll(alreadyParsed);
// Read the model and create bean definitions based on its content
if (this.reader == null) {
this.reader = new ConfigurationClassBeanDefinitionReader(
registry, this.sourceExtractor, this.resourceLoader, this.environment,
this.importBeanNameGenerator, parser.getImportRegistry());
}
//第二個解析的位置,這裡是加載configurationClasse中内部可能存在配置bean,比如方法上加了@Bean或者@Configuration标簽的bean
this.reader.loadBeanDefinitions(configClasses);
......
}
......
}
兩個解析的位置分别是:
- 第一個位置是
類的ConfigurationClassParser
方法,解析能夠直接從parse
中擷取的候選bean。beanFactory
- 第二個是
的ConfigurationClassBeanDefinitionReader
方法,解析從直接擷取的候選bean中内部定義的bean。loadBeanDefinitions
2.2.4
ConfigurationClassParser
的解析過程
ConfigurationClassParser
的
parse
有很多重載的方法,但是内部邏輯都是調用的
processConfigurationClass
方法。
public void parse(Set<BeanDefinitionHolder> configCandidates) {
for (BeanDefinitionHolder holder : configCandidates) {
BeanDefinition bd = holder.getBeanDefinition();
try {
//解析bean
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);
}
}
//處理DeferredImportSelector類型的實作類
this.deferredImportSelectorHandler.process();
}
protected final void parse(@Nullable String className, String beanName) throws IOException {
......
processConfigurationClass(new ConfigurationClass(reader, beanName));
}
protected final void parse(Class<?> clazz, String beanName) throws IOException {
processConfigurationClass(new ConfigurationClass(clazz, beanName));
}
protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
processConfigurationClass(new ConfigurationClass(metadata, beanName));
}
從上面可以看出來,主要要看的邏輯還是在
processConfigurationClass
方法中。這裡還需要注意一點就是在最上面的
parse
方法最後有一個邏輯
this.deferredImportSelectorHandler.process()
這個邏輯是處理
@Import
注解中指定的引入類是
DeferredImportSelectorHandler
子類的情況的。
進入到
processConfigurationClass
方法,關于這個方法在
@Conditional
注解解析的邏輯這個裡面說到過。這裡隻需要關注内部的
doProcessConfigurationClass
方法的邏輯。
protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
//檢查目前解析的配置bean是否包含Conditional注解,如果不包含則不需要跳過
// 如果包含了則進行match方法得到比對結果,如果是符合的并且設定的配置解析政策是解析階段不需要調過
if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
return;
}
//從緩存中嘗試擷取目前配置bean解析之後的ConfigurationClass對象
ConfigurationClass existingClass = this.configurationClasses.get(configClass);
if (existingClass != null) {
//檢查目前這個配置bean是通過@Import标簽引入的還是自動注入到另外一個配置類bean裡面的
if (configClass.isImported()) {
//如果是通過@Import标簽引入則将目前解析的配置bean加入到已經存在的解析過的bean的用來儲存通過@Import标簽引入的bean的集合中
if (existingClass.isImported()) {
existingClass.mergeImportedBy(configClass);
}
// Otherwise ignore new imported config class; existing non-imported class overrides it.
return;
}
else {
// Explicit bean definition found, probably replacing an import.
// Let's remove the old one and go with the new one.
//将目前解析的配置bean代替之前的
this.configurationClasses.remove(configClass);
this.knownSuperclasses.values().removeIf(configClass::equals);
}
}
// Recursively process the configuration class and its superclass hierarchy.
//遞歸擷取原始的配置類資訊然後封裝為SourceClass
SourceClass sourceClass = asSourceClass(configClass);
do {
//處理configClass
sourceClass = doProcessConfigurationClass(configClass, sourceClass);
}
while (sourceClass != null);
//儲存到configurationClasses中
this.configurationClasses.put(configClass, configClass);
}
在
doProcessConfigurationClass
方法中有很多标簽的處理邏輯,比如
@Component
,
@PropertySources
,
@ComponentScans
等。對于
@Import
注解的處理方式,封裝在了另外的一個方法
processImports
裡面。
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
throws IOException {
.......
processImports(configClass, sourceClass, getImports(sourceClass), true);
......
}
2.3 @Import
注解的解析
@Import
2.3.1
processImports
方法
已經知道
@Import
注解在那個方法裡面進行解析了。先看看對應的方法參數的含義。
參數 | 含義 |
---|---|
ConfigurationClass configClass | 目前需要解析的Configuration類 |
SourceClass currentSourceClass | 類的源資料封裝對象 |
Collection importCandidates | Configuration類引入的其他類,以及其他類中引入的别的類,比如A引入B,B引入C,C引入D。那麼這個就包含了B,C,D |
boolean checkForCircularImports | 是否檢查循環引入 |
現在對方法内部進行分析
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
Collection<SourceClass> importCandidates, boolean checkForCircularImports) {
//檢查包含有Import注解的集合是不是空的,空的則表示沒有
if (importCandidates.isEmpty()) {
return;
}
//檢查是否存在循環引入,通過deque的方式來檢查
if (checkForCircularImports && isChainedImportOnStack(configClass)) {
this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
}
else {
//将目前bean放到importStack中,用于檢查循環引入
this.importStack.push(configClass);
try {
for (SourceClass candidate : importCandidates) {
//import指定的Bean是ImportSelector類型
if (candidate.isAssignable(ImportSelector.class)) {
//執行個體化指定的ImportSelector子類
Class<?> candidateClass = candidate.loadClass();
ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
this.environment, this.resourceLoader, this.registry);
//如果是ImportSelector的子類DeferredImportSelector的子類則按照DeferredImportSelectorHandler邏輯進行處理
if (selector instanceof DeferredImportSelector) {
this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
}
else {//如果不是則擷取指定的需要引入的class的ClassNames
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
//根據ClassNames擷取并封裝成一個SourceClass
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
//繼續調用processImports處理
processImports(configClass, currentSourceClass, importSourceClasses, false);
}
}//如果是ImportBeanDefinitionRegistrar的子類
else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
// Candidate class is an ImportBeanDefinitionRegistrar -> delegate to it to register additional bean definitions
//如果對應的ImportBeanDefinitionRegistrar子類對象,并放到configClass,這個是用來注冊額外的bean的
Class<?> candidateClass = candidate.loadClass();
ImportBeanDefinitionRegistrar registrar =
ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
this.environment, this.resourceLoader, this.registry);
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
}
else {//不是上面任何類的子類就可以進行處理了,将指定的需要引入的bean轉化為ConfigurationClass,然後到processConfigurationClass方法中處理
this.importStack.registerImport(
currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
processConfigurationClass(candidate.asConfigClass(configClass));
}
}
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to process import candidates for configuration class [" +
configClass.getMetadata().getClassName() + "]", ex);
}
finally {
this.importStack.pop();
}
}
}
主要的步驟如下:
- 檢查解析出來的Import注解集合是不是空的,空的則不處理,直接傳回
- 如果設定了需要檢查循環依賴,則進行循環依賴的檢查,不需要則跳過,進入3步驟
-
進行處理
(1) 将目前bean放到
importStack
中,用于檢查循環引入
(2)循環處理以下情況
1)如果是
類型的子類。先執行個體化這個類,然後檢查這個類是不是ImportSelector
子類,是的則調用DeferredImportSelector
的内部類ConfigurationClassParser
的DeferredImportSelectorHandler
方法處理。如果不是handle
子類則繼續調用DeferredImportSelector
processImports
方法處理。按照步驟1從頭開始。
2)如果是
類型的子類,則執行個體化這個類,然後放到目前解析的bean的ImportBeanDefinitionRegistrar
importBeanDefinitionRegistrars
屬性中,後面注冊bean時候調用。
3)沒有實作以上任何接口,則将目前的被引入的bean加入到這個bean的
屬性中,然後調用importedBy
方法。processConfigurationClass
對上面的資訊進行總結。這個方法作用就是解析時候将被用
@Import
注解引入的bean加入到使用
@Import
注解标簽的bean的
importedBy
中後面進行解析時候用,還有就是後面注冊bean的時候可能也會調用實作了
ImportBeanDefinitionRegistrar
類型的子類。
2.3.2
processConfigurationClass
方法處理
configClass
上面
processImports
方法調用之後最後都會傳回到
processConfigurationClass
。這裡介紹以下這個方法的作用。這個方法會對
ConfigurationClass
對象(這個對象封裝了貼了
@Configuration
或者
@Bean
注解的對象的資訊)進行解析,解析上面的注解。就我們上面提到的
@Component
,
@PropertySources
,
@ComponentScans
等。最後儲存起來,後面執行個體化的時候會用到(後面執行個體化的時候使用的是CGLIB的方式執行個體化的),跟其他的bean不一樣。
protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
......
do {
//處理configClass
sourceClass = doProcessConfigurationClass(configClass, sourceClass);
}
while (sourceClass != null);
//儲存到configurationClasses中
this.configurationClasses.put(configClass, configClass);
}
到這裡
@Import
注解的解析基本就完了。剩下的就是介紹
ImportSelector
,
ImportBeanDefinitionRegistrar
以及
DeferredImportSelector
之間的差別了。下篇文章解析。
3. ImportSelector
, ImportBeanDefinitionRegistrar
以及 DeferredImportSelector
ImportSelector
ImportBeanDefinitionRegistrar
DeferredImportSelector
在上面的
processImports
方法中已經講解了對所有的
@Import
注解中
value
值為不同指的情況進行解析。有以下的情況:
-
接口的實作類ImportSelector
-
接口的實作類DeferredImportSelector
-
接口的實作類ImportBeanDefinitionRegistrar
- 非以上3中接口的實作類,也就是普通的類
這裡主要講解1到3這三個接口類的差別。
3.1 ImportSelector
ImportSelector
public interface ImportSelector {
String[] selectImports(AnnotationMetadata importingClassMetadata);
}
ImportSelector
接口作用将方法傳回的字元串數組作為bean注入到容器中,注意這裡的字元串需要是對象的全路徑名稱比如
A.class.getName()
這種。後面會講spring的擴充的時候會使用的。
3.2 ImportBeanDefinitionRegistrar
ImportBeanDefinitionRegistrar
ImportBeanDefinitionRegistrar
這個接口作用是,使用者可以實作了之後來自定義來注冊需要注冊的bean。可以設定自定義的
BeanNameGenerator
bean名稱生成規則。
public interface ImportBeanDefinitionRegistrar {
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,
BeanNameGenerator importBeanNameGenerator) {
registerBeanDefinitions(importingClassMetadata, registry);
}
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
}
}
3.3 DeferredImportSelector
DeferredImportSelector
ImportBeanDefinitionRegistrar
是
ImportSelector
的子接口,在4.0版本加入的。在這個類裡面添加分組的功能,能夠将多個
DeferredImportSelector
進行分組。同一個組内的
ImportBeanDefinitionRegistrar
能夠通過實作
@Order
注解去實作排序。在調用的時候處理的時候會先按照序号進行排序,然後依次調用對應實作的
ImportSelector
接口的
selectImports
方法。
還有一點就是
DeferredImportSelector
的調用邏輯在,所有的
@Configuration
已經解析了之後在調用的。這點可以在
2.2.4 ConfigurationClassParser的解析過程
代碼中看出來。
public interface DeferredImportSelector extends ImportSelector {
default Class<? extends Group> getImportGroup() {
return null;
}
interface Group {
......
}
這裡将差別列舉出來
類 | 作用 |
---|---|
| 将方法傳回的字元串數組作為bean注入到容器中 |
| 自定義來注冊bean |
| 跟 差不多隻不過多了分組的功能,處理在 類型bean解析之後 |