@Import模式向容器導入Bean是一種非常重要的方式,特別是在注解驅動的Spring項目中,@Enablexxx的設計模式中有大量的使用,在當下最流行的Spring Boot中,被用來做底層抽象、組件式的設計。
比如我們熟悉的:@EnableAsync、@EnableAspectJAutoProxy、@EnableMBeanExport、@EnableTransactionManagement…等等統一采用的都是借助@Import注解來實現的。
本篇文章旨在着眼於對@Import的使用上,以及結合ImportSelector、DeferredImportSelector、ImportBeanDefinitionRegistrar這三個接口的一些高級應用~。
需要注意的是:ImportSelector、DeferredImportSelector、ImportBeanDefinitionRegistrar這三個接口都必須依賴於@Import一起使用,而@Import可以單獨使用。
@Import注解
這里單獨使用@Import的例子,使用它有一個非常方便的地方在於:它可以導入Jar包里面的類(因為我們的@ComponentScan是不會掃描jar包的),可以看看下面這個例子:
//@Configuration換成@Component效果也是一樣的,習慣上放在@Configuration而已
@Configuration
@Import({AntPathMatcher.class}) // 這是Spring-code包里面的Bean,我隨便找的一個
public class MyConfig {
}
ImportSelector和DeferredImportSelector
使用@Import的時候,它的類可以是實現了ImportSelector或者DeferredImportSelector接口的類。
先來看看ImportSelector接口的定義,其中有兩個方法:
- String[] selectImports(AnnotationMetadata importingClassMetadata) 返回一個包含了類全限定名的數組,這些類會注入到Spring容器當中。
- Predicate<String> getExclusionFilter() 返回一個謂詞接口,該方法制定了一個對類全限定名的排除規則來過濾一些候選的導入類,默認不排除過濾。該接口可以不實現。
Spring容器會實例化這個實現類,並且執行其selectImports方法。
我們先來看一個ImportSelector的例子:
public class MyImportSelector implements ImportSelector
//雖然不能@Autowired,但是實現了這些接口是可以感知到的,下面看源碼會發現,Spring會給它注入進去
// 這樣我們就可以根據特定的條件,來決定某些Bean能注入,有些Bean不能注入了
//,BeanClassLoaderAware,BeanFactoryAware,EnvironmentAware,ResourceLoaderAware
{
// 備注:這里各種@Autowired的注入都是不生效的,都是null
// 了解Spring容器刷新過程的時候就知道,這個時候還沒有開始解析@Autowired,所以肯定是不生效的
@Autowired
private HelloService helloService;
/**
* 容器在會在特定的時機,幫我們調用這個方法,向容器里注入Bean信息
*
* @param importingClassMetadata 包含配置類上面所有的注解信息,以及該配置類本身
* 若有需要,可以根據這些其它注解信息,來判斷哪些Bean應該注冊進去,哪些不需要
* @return 返回String數組,注意:都必須是類的全類名,才會被注冊進去的(若你返回的全類名不存在該類,容器會拋錯)
*/
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
System.out.println("this MyImportSelector...");
//return new String[]{"com.buqiong.bean.Child"};
// 一般建議這么玩 用字符串寫死的方式只是某些特殊場合(比如這個類不一定存在之類的。。。)
return new String[]{Child.class.getName()};
}
}
這里我提一個Spring的默認實現AdviceModeImportSelector(它通過解析注解信息,選擇合適的Bean加入),這個類在事務的處理和AOP處理上有用到。
再來一個DeferredImportSelector的示例:
public class MyDeferredImportSelector implements DeferredImportSelector {
// 同樣的,它也只需要實現這個方法即可 但是它還提供了一些更高級的功能
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
System.out.println("this MyDeferredImportSelector...");
// 這里面若容器里已經有名為`com.fsx.bean.Child`的Bean,就不會再注冊進去了的
return new String[]{"com.buqiong.bean.Child"};
}
}
我們發現使用方式幾乎一樣,真的一樣嗎?其實容器啟動的時候還有一個細節輸出:
this MyImportSelector...
this MyDeferredImportSelector...
從現象和名字中,我們能夠更加直觀的看出來:DeferredImportSelector顯然是屬於延遲加載、靠后加載的,那到底有多延遲,他們執行時機都是啥時候呢? 這就是我們接下來討論的重點。
再次強調一次:實現此接口的Bean必須是放在@Import進去的才會生效,而不能直接@Bean加入進去
ImportSelector和DeferredImportSelector的區別:
- DeferredImportSelector是ImportSelector的一個擴展
- ImportSelector實例的selectImports方法的執行時機,是在@Configuration注解中的其他邏輯被處理之前,所謂的其他邏輯,包括對@ImportResource、@Bean這些注解的處理(注意,這里只是對@Bean修飾的方法的處理,並不是立即調用@Bean修飾的方法,這個區別很重要!)
- DeferredImportSelector實例的selectImports方法的執行時機,是在@Configuration注解中的其他邏輯被處理完畢之后
- DeferredImportSelector的實現類可以用Order注解,或者實現Ordered接口來對selectImports的執行順序排序(ImportSelector不支持)
- ImportSelector是Spring3.1提供的,DeferredImportSelector是Spring4.0提供的
- Spring Boot的自動配置功能就是通過DeferredImportSelector接口的實現類EnableAutoConfigurationImportSelector做到的(因為自動配置必須在我們自定義配置后執行才行,注意SpringBoot高版本已換成AutoConfigurationImportSelector)
分析Spring源碼中對此兩個接口的處理
結合 Spring解析@Configuration注解的處理器:ConfigurationClassPostProcessor(ConfigurationClassParser) 分析。public void parse(Set<BeanDefinitionHolder> configCandidates){ ... }的最后一步,才去處理實現了DeferredImportSelector接口的類,因此是非常滯后的(此時已經處理好了@Bean、@ComponentScan、@ImportResource等等事宜)。
ImportSelector 被設計成其實和@Import注解的類同樣的導入效果,但是實現 ImportSelector的類可以條件性地決定導入哪些配置。
DeferredImportSelector 的設計目的是在所有其他的配置類被處理后才處理。這也正是該語句被放到本函數最后一行的原因。
看看前面做了些什么,我們直接來到核心處理方法doProcessConfigurationClass:
@Nullable
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
throws IOException {
// Recursively process any member (nested) classes first
// 遞歸循環的解析內部類的配置類(因此,即使是內部類的配置類,我們Spring也是支持的,很強大有木有)
processMemberClasses(configClass, sourceClass);
// Process any @PropertySource annotations
// 處理@PropertySources注解和@PropertySource注解,交給processPropertySource去解析
// 顯然必須是ConfigurableEnvironment的環境采取解析,否則發出警告:會忽略這個不進行解析
for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), PropertySources.class,
org.springframework.context.annotation.PropertySource.class)) {
if (this.environment instanceof ConfigurableEnvironment) {
processPropertySource(propertySource);
}
else {
logger.warn("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
"]. Reason: Environment must implement ConfigurableEnvironment");
}
}
// Process any @ComponentScan annotations
// 解析@ComponentScans和@ComponentScan注解,進行包掃描。最終交給ComponentScanAnnotationParser#parse方法進行處理
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
if (!componentScans.isEmpty() &&
!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
for (AnnotationAttributes componentScan : componentScans) {
// The config class is annotated with @ComponentScan -> perform the scan immediately
Set<BeanDefinitionHolder> scannedBeanDefinitions =
this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
// Check the set of scanned definitions for any further config classes and parse recursively if needed
// 這一步非常重要:如果被掃描的Bean定義信息,還是屬於@Configuration的配置組件,那就繼續調用本類的parse方法,進行遞歸解析==============
// 所以我們在進行包掃描的時候,也是會掃描到@Configuration並且進行解析的。。。
for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
if (bdCand == null) {
bdCand = holder.getBeanDefinition();
}
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
parse(bdCand.getBeanClassName(), holder.getBeanName());
}
}
}
}
// Process any @Import annotations
// 這里是今天的主菜:解析@Import注解,后面詳解processImports方法
processImports(configClass, sourceClass, getImports(sourceClass), true);
// Process any @ImportResource annotations
// 顯然,先是處理了@Import,才過來解析@ImportResource的====最終交給environment.resolveRequiredPlaceholders(resource)去處理了
AnnotationAttributes importResource =
AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
if (importResource != null) {
String[] resources = importResource.getStringArray("locations");
Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
for (String resource : resources) {
String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
configClass.addImportedResource(resolvedResource, readerClass);
}
}
// Process individual @Bean methods
// 處理被標注了@Bean注解的方法們
// 遍歷@Bean注釋的方法,添加到configClass中的BeanMethod
// 這里需要注意的是:最終會實例化的時候是執行此工廠方法來獲取到對應實例的
// if (mbd.getFactoryMethodName() != null) { ... } 這里會是true,從而執行此方法內部邏輯。 原理同XML中的FactoryMethod方式創建Bean
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}
// Process default methods on interfaces
// 這個特別有意思:處理接口中被@Bean注解默認方法,代碼如下
// 因為JDK8以后接口可以寫default方法了,所以接口竟然也能給容器里注冊Bean了
// 但是需要注意:這里的意思並不是說你寫個接口然后標注上@Configuration注解,然后@Bean注入就可以了
// 這個解析的意思是我們的配置類可以實現接口,然后在所實現的接口里面若有@Bean的注解默認方法,是會加入到容器的
processInterfaces(configClass, sourceClass);
// Process superclass, if any
// 如果有父類的話,則返回父類進行進一步的解析,否則返回null
// 這個也是很厲害的,如果有父類,也是能夠繼續解析的。@EnableWebMvc中的DelegatingWebMvcConfiguration就是這么玩的
// 它自己標注了@Configuration注解,但是真正@Bean注入,都是它父類去干的
if (sourceClass.getMetadata().hasSuperClass()) {
String superclass = sourceClass.getMetadata().getSuperClassName();
if (superclass != null && !superclass.startsWith("java") &&
!this.knownSuperclasses.containsKey(superclass)) {
this.knownSuperclasses.put(superclass, configClass);
// Superclass found, return its annotation metadata and recurse
// 若找到了父類,會返回然后繼續處理
return sourceClass.getSuperClass();
}
}
// No superclass -> processing is complete
// 沒有父類,就停止了,處理結束
return null;
}
我們重點來看看processImports這個方法,如下:
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
Collection<SourceClass> importCandidates, boolean checkForCircularImports) {
// 相當於沒有找到@Import注解,那就不處理了
// 說明:獲取@Import是遞歸獲取,任意子類父類上標注有都行的
if (importCandidates.isEmpty()) {
return;
}
//循環依賴檢查:如果存在循環依賴的話,則直接拋出異常(比如你@Import我,我@Import你這種情況)
if (checkForCircularImports && isChainedImportOnStack(configClass)) {
this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
}
else {
this.importStack.push(configClass);
try {
// 依次處理每個@Import里面候選的Bean們
for (SourceClass candidate : importCandidates) {
// 分之一:如果實現了ImportSelector接口(又分為兩種,因為有子接口DeferredImportSelector呢)
if (candidate.isAssignable(ImportSelector.class)) {
// Candidate class is an ImportSelector -> delegate to it to determine imports
Class<?> candidateClass = candidate.loadClass();
// 根據空的構造函數,把這個Bean實例化出來,
ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
// 這里面注入了一下感知接口的元素,包括environment、resourceLoader、registry等等(實現了DeferredImportSelector也在此處注入了哦)
ParserStrategyUtils.invokeAwareMethods(selector, this.environment, this.resourceLoader, this.registry);
// 判斷是否是DeferredImportSelectorHolder的子類,是的話先加入進入 不處理先
if (this.deferredImportSelectors != null && selector instanceof DeferredImportSelector) {
this.deferredImportSelectors.add(
new DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector));
}
// 否則立馬調用它的`selectImports`方法,拿到一個BeanName的數組
else {
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
// 這里面高級了:因為我們這里放進去的Bean,有可能是普通Bean,當然也還有可能是實現了ImportSelector等等接口的,因此此處繼續調用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
Class<?> candidateClass = candidate.loadClass();
ImportBeanDefinitionRegistrar registrar =
BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);
ParserStrategyUtils.invokeAwareMethods(
registrar, this.environment, this.resourceLoader, this.registry);
// 完成了實例化后和Aware方法后,添加進configClass類的屬性importBeanDefinitionRegistrars里先緩存着(至於執行時機,留給下面講吧)
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
}
else {
// Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
// process it as an @Configuration class
// 什么都接口都沒有實現,那就是普通的配置類嘛,那就直接交給processConfigurationClass()去處理了
// 備注:這個方法的處理流程,請參照上面哦
// 這里面有個特別重要的地方:是candidate.asConfigClass(configClass)這一句,給包裝陳一個ConfigurationClass去處理
// 因為傳入了configClass屬於它的importedBy屬性,這樣一來ConfigurationClass#isImported()就返回true了,表面這個Bean是被單純的、單純的、單純的的導入進來的
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 {
// 上面push,下面pop出來
this.importStack.pop();
}
}
}
從上面的源碼處理過程,我們可以很清楚的知道了ImportSelector#selectImports執行時機,然后並且把DeferredImportSelector和ImportBeanDefinitionRegistrar都先裝起來了。
doProcessConfigurationClass執行完成之后,processConfigurationClass也就執行完了,接下來就開始執行頂層parse方法內部的:processDeferredImportSelectors():
DeferredImportSelector的源碼:(Spring4和Spring5差異很大) 本文都是基於Spring5進行講解的
// Spring4的源碼,啥都木有
public interface DeferredImportSelector extends ImportSelector {
}
// Sparing5的源碼,加了不少東西
public interface DeferredImportSelector extends ImportSelector {
@Nullable
default Class<? extends Group> getImportGroup() {
return null;
}
// 內部接口
interface Group {
void process(AnnotationMetadata metadata, DeferredImportSelector selector);
Iterable<Entry> selectImports();
// 內部的內部類
class Entry {
private final AnnotationMetadata metadata;
private final String importClassName;
public Entry(AnnotationMetadata metadata, String importClassName) {
this.metadata = metadata;
this.importClassName = importClassName;
}
public AnnotationMetadata getMetadata() {
return this.metadata;
}
public String getImportClassName() {
return this.importClassName;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Entry entry = (Entry) o;
return Objects.equals(this.metadata, entry.metadata) &&
Objects.equals(this.importClassName, entry.importClassName);
}
@Override
public int hashCode() {
return Objects.hash(this.metadata, this.importClassName);
}
}
}
}
源碼的差異很大,就造成了processDeferredImportSelectors的處理方式不盡相同。
private void processDeferredImportSelectors() {
List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
this.deferredImportSelectors = null;
if (deferredImports == null) {
return;
}
// 排序:注意這個比較器。它是按照PriorityOrdered、Ordered等進行優先級排序的
// 因此我們可以看到一大特性:DeferredImportSelector是支持Order排序的
deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);
// 這個Map厲害了,key竟然是Object。。。
Map<Object, DeferredImportSelectorGrouping> groupings = new LinkedHashMap<>();
Map<AnnotationMetadata, ConfigurationClass> configurationClasses = new HashMap<>();
// 對這些個DeferredImportSelector一個個處理吧
//遍歷DeferredImportSelector接口集合,獲取Group集合類,默認為DefaultDeferredImportSelectorGroup
for (DeferredImportSelectorHolder deferredImport : deferredImports) {
// getImportGroup()方法是DeferredImportSelector接口的default方法,若不復寫,默認return null
// 該接口的作用是:子類可以對一些Import的類進行分類
//Group 為DeferredImportSelector的一個內部接口~~~~~~~~~~~
Class<? extends Group> group = deferredImport.getImportSelector().getImportGroup();
// 按照group 或者 deferredImport 進行分組
DeferredImportSelectorGrouping grouping = groupings.computeIfAbsent((group == null ? deferredImport : group), (key) -> new DeferredImportSelectorGrouping(createGroup(group)));
grouping.add(deferredImport);
configurationClasses.put(deferredImport.getConfigurationClass().getMetadata(),
deferredImport.getConfigurationClass());
}
//遍歷Group集合,作用也是調用processImport()方法用於解析@Import
for (DeferredImportSelectorGrouping grouping : groupings.values()) {
grouping.getImports().forEach((entry) -> {
ConfigurationClass configurationClass = configurationClasses.get(
entry.getMetadata());
try {
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);
}
});
}
DeferredImportSelector接口在Spring-core/Context中沒有實現類。但是在Spring Boot的自動配置中有大量的實現。
ImportBeanDefinitionRegistrar
該接口功能非常強大,能夠實現快速的、批量的、掃描式的注冊。比如我們熟悉的ServletComponentScanRegistrar就是去解析注解@ServletComponentScan實現批量注冊Bean定義。
MapperScannerRegistrar就是MyBatis用來解析@MapperScan注解,來掃描的 等等還有很多類似的設計方式。
先看一個最簡單的效果吧:
@Configuration
@Import({Parent.class, AntPathMatcher.class, MyImportSelector.class, MyDeferredImportSelector.class, MyImportBeanDefinitionRegistrar.class})
public class MyConfig {
}
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
// 同樣的,這種注入都是不好使的(相同的,那些感知接口是可以實現的,從而注入對應組件)
@Autowired
private HelloService helloService;
/**
* 實現了該接口讓我們的這個類成為了擁有注冊bean的能力
* 也可以讓我們實現動態注入(根據條件、邏輯進行動態注入)
*
* @param importingClassMetadata 注解信息和本類信息
* @param registry 注冊器,我們可以向容器里面注冊[Bean定義信息]
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
System.out.println("this MyImportBeanDefinitionRegistrar");
RootBeanDefinition beanDefinition = new RootBeanDefinition();
beanDefinition.setBeanClass(GenericBean.class);
registry.registerBeanDefinition("genericBean", beanDefinition);
}
}
發現:genericBean成功被注入容器了。
從另外一個日志的打印來看:MyImportBeanDefinitionRegistrar是更加滯后執行的。那么下面我們就要看看它到底啥時候執行的呢?
this MyImportSelector...
this MyDeferredImportSelector...
this MyImportBeanDefinitionRegistrar
在ConfigurationClassParser中,如果實現了ImportBeanDefinitionRegistrar接口的,最后是這么一句話給緩存下來了(還木有執行):
// registrar是已經被實例化了的當前類
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
下面重點看看,該接口到底什么時候執行的呢?根據斷點,跟蹤到它的加載時機是在ConfigurationClassPostProcessor這句代碼里:
this.reader.loadBeanDefinitions(configClasses);:
public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
// 依次解析每一個配置類,這里面注意了configClass 比如我們的RootConfig這個對象里有個字段
//importBeanDefinitionRegistrars是記錄着了我們前面add進去的ImportBeanDefinitionRegistrar的,因此它會在此處開始執行了
for (ConfigurationClass configClass : configurationModel) {
loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
}
}
// 這個方法很重要:它處理了多種方式(@Bean、實現接口類注冊等等)完成向容器里注冊Bean定義信息
private void loadBeanDefinitionsForConfigurationClass(
ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
// 如果這咯Config不需要被解析,做一些清理、移除的操作~~~~
if (trackedConditionEvaluator.shouldSkip(configClass)) {
String beanName = configClass.getBeanName();
if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
this.registry.removeBeanDefinition(beanName);
}
this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
return;
}
///稍微注意一下Spring處理這些Bean定義的順序,在某些判斷邏輯中或許能用到///
// 如果是被單純@Import進來的,這個值是true的,默認值是false哦
if (configClass.isImported()) {
// 這個處理源碼這里不分析了,比較簡單。支持@Scope、@Lazy、@Primary、@DependsOn、@Role、@Description等等一些通用的基本屬性
registerBeanDefinitionForImportedConfigurationClass(configClass);
}
//處理方法上的@Bean 解析@Bean上面各種屬性值。也處理上面提到的那些通用注解@Lazy等等吧
//這里面只說一個內部比較重要的方法isOverriddenByExistingDefinition(beanMethod, beanName)
// 該方法目的主要是去重。其實如果是@Configuration里面Bean重名了,IDEA類似工具發現,但是無法判斷xml是否也存在(注意,發現歸發現,但並不是編譯報錯哦~~~)
// 它的處理策略為:若來自同一個@Configuration配置類,那就保留之前的。若來自不同配置類,那就覆蓋
for (BeanMethod beanMethod : configClass.getBeanMethods()) {
loadBeanDefinitionsForBeanMethod(beanMethod);
}
//處理@ImportResource,里面解析xml就是上面說到的解析xml的XmlBeanDefinitionReader
//所以,咱們@Configuration和xml是可以並行使用的
loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
// 最后,這里就是咱們今天的主菜了:解析咱們的ImportBeanDefinitionRegistrars
// configClass.getImportBeanDefinitionRegistrars():就是我們上面異步add進去的那些注冊器們
loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}
loadBeanDefinitionsFromRegistrars完整解析:
// 沒什么多余的代碼 所有的注冊邏輯(哪些Bean需要注冊,哪些不需要之類的,全部交給子類去實現)
// 用處:上面有提到,比如@MapperScan這種批量掃描的===
private void loadBeanDefinitionsFromRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars) {
registrars.forEach((registrar, metadata) ->
registrar.registerBeanDefinitions(metadata, this.registry));
}
應用場景分析
根據各個接口的特點,有各自的應用場景。因為直接@Import普通類的場景相對較少,這里主要說說實現接口的方式的場景:
ImportSelector接口應用場景
AdviceModeImportSelector:它是個抽象類。(實現類有出名的AsyncConfigurationSelector、CachingConfigurationSelector等,因為都是基於代理來做的,所以都繼承了此抽象)。
它拿到泛型類型(比如@EnableAsync或者@EnableCaching),然后解析注解為AnnotationAttributes,最后由子類去實現select邏輯(具體要向容器注入的Class全類名),比如注入ProxyAsyncConfiguration,或者其它的。
總之,像這種還不能決定去注入哪個處理器(如果你能決定,那就直接@Import那個類好了,沒必要實現接口了嘛),然后可以實現此接口,寫出一些判斷邏輯,不同的配置情況注入不同的處理類。
DeferredImportSelector接口應用場景
它和上面只是執行的時機不同。在Spring內部沒有應用,但是在Spring Boot中卻有大量的應用,比如:AutoConfigurationImportSelector、EnableCircuitBreakerImportSelector等等。
實現這個接口的基本思想是:默認處理(以用戶配置的為准,若用戶沒管,那就執行我的默認配置唄)。執行生效的一個先后順序的簡單控制
ImportBeanDefinitionRegistrar接口應用場景
它的應用場景特別的有用,因此也是最常使用的。因為它直接可以向工廠里注冊Bean的定義信息(當然也可以拿出來Bean定義信息,做出對應的修改)
下面兩個實現,都和@EnableAspectJAutoProxy注解相關:
- AspectJAutoProxyRegistrar:它能解析注解的時候,從BeanFactory拿出指定的Bean,設置一些參數值等等
- AutoProxyRegistrar:自動代理的注冊器。它和上面的區別在於它和代理的類型無關(它可以指定mode類型),而上面是表示就是用AspectJ來做切面代理。
實現它的基本思想是:當自己需要操作BeanFactory里面的Bean的時候,那就必須只有它才能做到了。而且它還有個方便的地方,那就是做包掃描的時候,比如@MapperScan類似這種的時候,用它處理更為方便(因為掃描到了直接注冊即可)。
@Mapper的掃描依賴於ClassPathMapperScanner,它由mybatis-spring提供。它繼承於ClassPathBeanDefinitionScanner,由Spring底層提供
總結
注意,完成了所有的這些注解、配置文件的解析,Bean們都還只是被加載了,還沒有加入到Bean的定義信息里面,更別談實例化了。要加入到Bean的定義信息里面存儲好,還得這一步:
//configClasses就是parser.parse(candidates);上面解析完成了的配置類
//根據這些已經解析好了的配置類,由這個ConfigurationClassBeanDefinitionReader去加載Bean定義信息
this.reader.loadBeanDefinitions(configClasses);
| 參考: |
