《Spring核心技術》第5章:三萬字深度解析@Import註解
作者:冰河
星球:
部落格:
文章彙總:/md/all/all.html
原始碼地址:
沉澱,成長,突破,幫助他人,成就自我。
大家好,我是冰河~~
「本章難度」:★★★★☆ 「本章重點」:進一步學習並掌握@Import註解向Spring IOC容器中注入Bean的示例與流程,從原始碼級別徹底掌握@Import註解在Spring底層的執行流程。
本節目錄如下所示:
學習指引 註解說明 註解原始碼 註解使用場景 使用案例 引入普通類案例 引入實現了ImportSelector介面的類案例 引入實現了ImportBeanDefinitionRegistrar介面的類案例 原始碼時序圖 原始碼解析 總結 思考 VIP服務
一、學習指引
@Import註解是什麼?
想深入學習一項技術並不是一朝一夕就能夠完成的,它需要我們花費大量的時間和精力,塌下心來深入研究,從不知道,到了解,再到熟悉,最後到精通,這需要一個不斷深入研究,不斷實踐的過程。
學習Spring亦是如此,要想掌握好Spring的核心技術,同樣需要塌下心來不斷研究和實踐。
二、註解說明
關於@Import註解的一點點說明~~
@Import註解可以將第三方包中的類物件注入到IOC容器中。使用Spring開發業務系統時,@Import註解的使用頻率不及@Bean註解,@Import註解往往在一些中介軟體或者框架專案中使用的比較多。
在Spring底層,也大量使用了@Import註解來向IOC容器中注入Bean物件。當然,如果在開發業務系統時,也可以使用@Import註解向IOC容器中注入Bean物件。@Import註解相比於@Bean註解來講,在使用上會更加靈活。
2.1 註解原始碼
@Import註解只能標註到類或其他註解上,通常與配置類一起使用的,使用此註解引入的類上可以不再使用@Configuration,@Component等註解標註。本節,就對@Import註解的原始碼進行簡單的剖析。
@Import註解的原始碼詳見:org.springframework.context.annotation.Import,如下所示。
/**
* Since: 3.0
* @author Chris Beams
* @author Juergen Hoeller
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
Class<?>[] value();
}
從@Import原始碼的註釋可以看出,@Import是Spring從3.0版本開始提供的註解,註解中只有一個Class陣列型別的value屬性。含義如下所示。
value:Class陣列型別,用於指定其他配置類的位元組碼,支援指定多個配置類。另外,使用value屬性指定的有一定的條件,必須是普通類、實現了ImportSelector介面的類和實現了ImportBeanDefinitionRegistrar介面的類。
注意:@Import註解只能標註到類上。
2.2 註解使用場景
在使用Spring進行開發時,如果涉及到的配置項比較多,要是將所有的配置項都寫到一個類裡,則配置結構和配置內容將會變得非常雜亂,如果此時使用@Import註解,則可以將配置項進行分類管理。
另外,如果在專案中需要引入第三方的類,並且需要將這些類的物件注入到IOC容器中,也可以使用@Import註解。
三、使用案例
@Import註解案例實戰~~
@Import註解可以引入三種類,分別如下所示。
引入普通類,將Bean物件注入到IOC容器。 引入實現了ImportSelector介面的類,將selectImports()方法返回的Bean陣列注入到IOC容器,但是實現了ImportSelector介面的類物件本身不會被註冊到IOC容器中。 引入實現了ImportBeanDefinitionRegistrar介面的類,使用registerBeanDefinitions()方法中的BeanDefinitionRegistry物件注入BeanDefinition物件到IOC容器中,但是實現了ImportBeanDefinitionRegistrar介面的類物件本身不會被註冊到IOC容器中。
3.1 引入普通類案例
本節,主要實現使用@Import註解實現引入普通類,並且將Bean物件注入到IOC容器中的案例。具體實現步驟如下所示。
(1)新建User類
User類的原始碼詳見:spring-annotation-chapter-05工程下的io.binghe.spring.annotation.chapter05.bean.User,如下所示。
public class User {
private Long userId;
private String userName;
//#############省略getter/serrer方法############
}
可以看到,User類就是一個普通的類物件,後續會透過@Import註解引入User類,並且將User類的物件注入到IOC容器中。
(2)新建Spring配置類ImportConfig
ImportConfig類的原始碼詳見:spring-annotation-chapter-05工程下的io.binghe.spring.annotation.chapter05.config.ImportConfig,如下所示。
@Import(value = {User.class})
@Configuration
public class ImportConfig {
}
可以看到,ImportConfig類主要是Spring的配置類,會在ImportConfig類上標註@Configuration註解和@Import註解,並且會透過@Import註解引入User類,將User類的物件注入到IOC容器中。
(3)新建ImportTest類
ImportTest類的原始碼詳見:spring-annotation-chapter-05工程下的io.binghe.spring.annotation.chapter05.ImportTest,如下所示。
public class ImportTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ImportConfig.class);
String[] definitionNames = context.getBeanDefinitionNames();
Arrays.stream(definitionNames).forEach((definitionName) -> System.out.println(definitionName));
}
}
可以看到,ImportTest類主要是案例的測試類,在ImportTest類的main()方法中,主要列印了Bean定義的名稱。
(4)執行ImportTest類
執行ImportTest類的main()方法,輸出的結果資訊如下所示。
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
importConfig
io.binghe.spring.annotation.chapter05.bean.User
其中,以org.springframework
包命名的Bean是Spring內部的Bean。另外,可以看到,結果資訊中也輸出了ImportConfig類的Bean名稱和User類的Bean名稱。
說明:使用@Import註解可以引入普通的類,並且能夠將類物件注入到Spring容器中。
3.2 引入實現了ImportSelector介面的類案例
本節,主要實現使用@Import註解引入實現了ImportSelector介面的類,將selectImports()方法返回的Bean陣列注入到IOC容器中的案例。具體的實現步驟如下所示。
注意:本節實現的案例是在3.1節的基礎上實現的。
(1)新建ImportSelectorBean類
ImportSelectorBean類的原始碼詳見:spring-annotation-chapter-05工程下的io.binghe.spring.annotation.chapter05.bean.ImportSelectorBean,如下所示。
public class ImportSelectorBean {
private Long id;
private String name;
//########省略getter/setter方法#########
}
可以看到,ImportSelectorBean類是一個普通的類,ImportSelectorBean類的物件後續會透過ImportSelector介面的selectImports()注入到IOC容器中。
(2)新建MyImportSelector類
MyImportSelector類的原始碼詳見:spring-annotation-chapter-05工程下的io.binghe.spring.annotation.chapter05.selector.MyImportSelector,如下所示。
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{ImportSelectorBean.class.getName()};
}
}
可以看到,MyImportSelector類實現了ImportSelector介面,並實現了ImportSelector介面的selectImports()方法,在selectImports()中返回了包含ImportSelectorBean類的全類名的Spring陣列。
(3)修改ImportConfig類
ImportConfig類的原始碼詳見:spring-annotation-chapter-05工程下的io.binghe.spring.annotation.chapter05.config.ImportConfig,如下所示。
@Import(value = {User.class, MyImportSelector.class})
@Configuration
public class ImportConfig {
}
可以看到,在ImportConfig類上標註的@Import註解的value屬性中,新增MyImportSelector類的Class物件。
(4)執行ImportTest類
執行ImportTest類的main()方法,輸出的結果資訊如下所示。
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
importConfig
io.binghe.spring.annotation.chapter05.bean.User
io.binghe.spring.annotation.chapter05.bean.ImportSelectorBean
可以看到,在輸出的結果資訊中,除了有Spring內部的Bean物件的名稱、ImportConfig類的Bean物件名稱和User類的Bean物件名稱外,還輸出了ImportSelectorBean類的Bean物件名稱。但是,並沒有輸出實現了ImportSelector介面的MyImportSelector類的Bean物件的名稱。
說明:使用@Import註解可以引入實現了ImportSelector介面的類,將selectImports()方法返回的Bean陣列注入到IOC容器中,但是實現了ImportSelector介面的類物件本身不會被註冊到IOC容器中。
3.3 引入實現了ImportBeanDefinitionRegistrar介面的類案例
本節,主要實現使用@Import註解引入實現了ImportBeanDefinitionRegistrar介面的類,使用registerBeanDefinitions()方法中的BeanDefinitionRegistry物件注入BeanDefinition物件到IOC容器中的案例。具體實現步驟如下所示。
(1)新增ImportBeanDefinitionRegistrarBean類
ImportBeanDefinitionRegistrarBean類的原始碼詳見:spring-annotation-chapter-05工程下的io.binghe.spring.annotation.chapter05.bean.ImportBeanDefinitionRegistrarBean,如下所示。
public class ImportBeanDefinitionRegistrarBean {
private Long id;
private String name;
//#########省略getter/setter方法############
}
可以看到,ImportBeanDefinitionRegistrarBean類就是一個普通的類,後續會透過ImportBeanDefinitionRegistrar介面的實現類實現的registerBeanDefinitions()方法將ImportBeanDefinitionRegistrarBean類的Bean物件注入到IOC容器中。
(2)新增MyImportBeanDefinitionRegistrar類
MyImportBeanDefinitionRegistrar類的原始碼詳見:spring-annotation-chapter-05工程下的io.binghe.spring.annotation.chapter05.registrar.MyImportBeanDefinitionRegistrar,如下所示。
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
String beanName = ImportBeanDefinitionRegistrarBean.class.getName();
BeanDefinition beanDefinition = new RootBeanDefinition(ImportBeanDefinitionRegistrarBean.class);
registry.registerBeanDefinition(beanName, beanDefinition);
}
}
可以看到,MyImportBeanDefinitionRegistrar類實現了ImportBeanDefinitionRegistrar介面,並實現了ImportBeanDefinitionRegistrar介面的registerBeanDefinitions()方法。在registerBeanDefinitions()方法中,獲取ImportBeanDefinitionRegistrarBean類的全類名作為注入到IOC容器中的Bean名稱。接下來,呼叫RootBeanDefinition類的構造方法傳入ImportBeanDefinitionRegistrarBean類的Class物件建立BeanDefinition物件。最終,呼叫registry的registerBeanDefinition()方法將建立的BeanDefinition物件注入到IOC容器中。
(3)修改ImportConfig類
ImportConfig類的原始碼詳見:spring-annotation-chapter-05工程下的io.binghe.spring.annotation.chapter05.config.ImportConfig,如下所示。
@Import(value = {User.class, MyImportSelector.class, MyImportBeanDefinitionRegistrar.class})
@Configuration
public class ImportConfig {
}
可以看到,在ImportConfig類上標註的@Import註解的value屬性中,新增了實現了ImportBeanDefinitionRegistrar介面的MyImportBeanDefinitionRegistrar類的Class物件。
(4)執行ImportTest類
執行ImportTest類的main()方法,輸出的結果資訊如下所示。
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
importConfig
io.binghe.spring.annotation.chapter05.bean.User
io.binghe.spring.annotation.chapter05.bean.ImportSelectorBean
io.binghe.spring.annotation.chapter05.bean.ImportBeanDefinitionRegistrarBean
可以看到,在輸出的結果資訊中,除了Spring內部的Bean名稱、ImportConfig類的Bean名稱、User類的Bean名稱和ImportSelectorBean類的Bean名稱外,還輸出了ImportBeanDefinitionRegistrarBean類的名稱。但是並沒有輸出實現了ImportBeanDefinitionRegistrar介面的MyImportBeanDefinitionRegistrar類的Bean名稱。
說明:使用@Import註解能夠引入實現了ImportBeanDefinitionRegistrar介面的類,使用registerBeanDefinitions()方法中的BeanDefinitionRegistry物件注入BeanDefinition物件到IOC容器中,但是實現了ImportBeanDefinitionRegistrar介面的類物件本身不會被註冊到IOC容器中。
四、原始碼時序圖
結合時序圖理解原始碼會事半功倍,你覺得呢?
本節,就以原始碼時序圖的方式,直觀的感受下@Import註解在Spring原始碼層面的執行流程。@Import註解在Spring原始碼層面的執行流程如圖5-1~5-3所示。
由圖5-1~圖5-3可以看出,@Import註解在Spring原始碼層面的執行流程會涉及到ImportTest類、AnnotationConfigApplicationContext類、AbstractApplicationContext類、PostProcessorRegistrationDelegate類、ConfigurationClassPostProcessor類、ConfigurationClassParser類、MyImportSelector類、ConfigurationClassBeanDefinitionReader類、ImportBeanDefinitionRegistrar類、MyImportBeanDefinitionRegistrar類和DefaultListableBeanFactory類。具體的原始碼執行細節參見原始碼解析部分。
五、原始碼解析
原始碼時序圖整清楚了,那就整原始碼解析唄!
@Import註解在Spring原始碼層面的執行流程,結合原始碼執行的時序圖,會理解的更加深刻。
(1)執行案例程式啟動類
案例程式啟動類原始碼詳見:spring-annotation-chapter-05工程下的io.binghe.spring.annotation.chapter05.ImportTest,執行ImportTest類的main()方法。
在ImportTest類的main()方法中呼叫了AnnotationConfigApplicationContext類的構造方法,並傳入了ImportConfig類的Class物件來建立IOC容器。接下來,會進入AnnotationConfigApplicationContext類的構造方法。
(2)解析AnnotationConfigApplicationContext類的AnnotationConfigApplicationContext(Class<?>... componentClasses)構造方法
原始碼詳見:org.springframework.context.annotation.AnnotationConfigApplicationContext#AnnotationConfigApplicationContext(Class<?>... componentClasses)。
public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
this();
register(componentClasses);
refresh();
}
可以看到,在上述構造方法中,呼叫了refresh()方法來重新整理IOC容器。
(3)解析AbstractApplicationContext類的refresh()方法
原始碼詳見:org.springframework.context.support.AbstractApplicationContext#refresh()。
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
//############省略其他程式碼##############
try {
//############省略其他程式碼##############
invokeBeanFactoryPostProcessors(beanFactory);
//############省略其他程式碼##############
}catch (BeansException ex) {
//############省略其他程式碼##############
}finally {
//############省略其他程式碼##############
}
}
}
refresh()方法是Spring中一個非常重要的方法,很多重要的功能和特性都是透過refresh()方法進行注入的。可以看到,在refresh()方法中,呼叫了invokeBeanFactoryPostProcessors()方法。
(4)解析AbstractApplicationContext類的invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory)方法
原始碼詳見:org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory)。
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
if (!NativeDetector.inNativeImage() && beanFactory.getTempClassLoader() == null && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
}
}
可以看到,在AbstractApplicationContext類的invokeBeanFactoryPostProcessors()方法中呼叫了PostProcessorRegistrationDelegate類的invokeBeanFactoryPostProcessors()方法。
(5)解析PostProcessorRegistrationDelegate類的invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory, List
原始碼詳見:org.springframework.context.support.PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory, List
由於方法的原始碼比較長,這裡,只關注當前最核心的邏輯,如下所示。
public static void invokeBeanFactoryPostProcessors(
ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {
//############省略其他程式碼##############
List<BeanDefinitionRegistryPostProcessor> currentRegistryProcessors = new ArrayList<>();
// First, invoke the BeanDefinitionRegistryPostProcessors that implement PriorityOrdered.
String[] postProcessorNames =
beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
for (String ppName : postProcessorNames) {
if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
processedBeans.add(ppName);
}
}
sortPostProcessors(currentRegistryProcessors, beanFactory);
registryProcessors.addAll(currentRegistryProcessors);
invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry, beanFactory.getApplicationStartup());
currentRegistryProcessors.clear();
// Next, invoke the BeanDefinitionRegistryPostProcessors that implement Ordered.
postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
for (String ppName : postProcessorNames) {
if (!processedBeans.contains(ppName) && beanFactory.isTypeMatch(ppName, Ordered.class)) {
currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
processedBeans.add(ppName);
}
}
sortPostProcessors(currentRegistryProcessors, beanFactory);
registryProcessors.addAll(currentRegistryProcessors);
invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry, beanFactory.getApplicationStartup());
currentRegistryProcessors.clear();
// Finally, invoke all other BeanDefinitionRegistryPostProcessors until no further ones appear.
boolean reiterate = true;
while (reiterate) {
reiterate = false;
postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
for (String ppName : postProcessorNames) {
if (!processedBeans.contains(ppName)) {
currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
processedBeans.add(ppName);
reiterate = true;
}
}
sortPostProcessors(currentRegistryProcessors, beanFactory);
registryProcessors.addAll(currentRegistryProcessors);
invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry, beanFactory.getApplicationStartup());
currentRegistryProcessors.clear();
}
//############省略其他程式碼##############
}
可以看到,在PostProcessorRegistrationDelegate類的invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory, List
(6)解析PostProcessorRegistrationDelegate類的invokeBeanDefinitionRegistryPostProcessors(Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors, BeanDefinitionRegistry registry, ApplicationStartup applicationStartup)方法
原始碼詳見:org.springframework.context.support.PostProcessorRegistrationDelegate#invokeBeanDefinitionRegistryPostProcessors(Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors, BeanDefinitionRegistry registry, ApplicationStartup applicationStartup)。
private static void invokeBeanDefinitionRegistryPostProcessors(
Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors, BeanDefinitionRegistry registry, ApplicationStartup applicationStartup) {
for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {
StartupStep postProcessBeanDefRegistry = applicationStartup.start("spring.context.beandef-registry.post-process")
.tag("postProcessor", postProcessor::toString);
postProcessor.postProcessBeanDefinitionRegistry(registry);
postProcessBeanDefRegistry.end();
}
}
可以看到,在invokeBeanDefinitionRegistryPostProcessors()方法中,會迴圈遍歷postProcessors集合中的每個元素,呼叫postProcessBeanDefinitionRegistry()方法註冊Bean的定義資訊。
(7)解析ConfigurationClassPostProcessor類的postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)方法
原始碼詳見:org.springframework.context.annotation.ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)。
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
//##########省略其他程式碼###################
processConfigBeanDefinitions(registry);
}
可以看到,在postProcessBeanDefinitionRegistry()方法中,會呼叫processConfigBeanDefinitions()方法。
(8)解析ConfigurationClassPostProcessor類的processConfigBeanDefinitions(BeanDefinitionRegistry registry)方法
原始碼詳見:org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions(BeanDefinitionRegistry registry)。
這裡,重點關注方法中的如下邏輯。
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
//############省略其他程式碼#################
// Parse each @Configuration class
ConfigurationClassParser parser = new ConfigurationClassParser(
this.metadataReaderFactory, this.problemReporter, this.environment,
this.resourceLoader, this.componentScanBeanNameGenerator, registry);
Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
do {
StartupStep processConfig = this.applicationStartup.start("spring.context.config-classes.parse");
parser.parse(candidates);
parser.validate();
//############省略其他程式碼#################
this.reader.loadBeanDefinitions(configClasses);
alreadyParsed.addAll(configClasses);
processConfig.tag("classCount", () -> String.valueOf(configClasses.size())).end();
//############省略其他程式碼#################
}
while (!candidates.isEmpty());
//############省略其他程式碼#################
}
可以看到,在processConfigBeanDefinitions()方法中,建立了一個ConfigurationClassParser型別的物件parser,並且呼叫了parser的parse()方法來解析類的配置資訊。
(9)解析ConfigurationClassParser類的parse(Set
原始碼詳見:org.springframework.context.annotation.ConfigurationClassParser#parse(Set
public void parse(Set<BeanDefinitionHolder> configCandidates) {
for (BeanDefinitionHolder holder : configCandidates) {
BeanDefinition bd = holder.getBeanDefinition();
try {
if (bd instanceof AnnotatedBeanDefinition) {
parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
}
//###############省略其他程式碼###############
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
}
}
this.deferredImportSelectorHandler.process();
}
可以看到,在ConfigurationClassParser類的parse(Set
(10)解析ConfigurationClassParser類的parse(AnnotationMetadata metadata, String beanName)方法
原始碼詳見:org.springframework.context.annotation.ConfigurationClassParser#parse(AnnotationMetadata metadata, String beanName)
protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
processConfigurationClass(new ConfigurationClass(metadata, beanName), DEFAULT_EXCLUSION_FILTER);
}
可以看到,上述parse()方法的實現比較簡單,直接呼叫了processConfigurationClass()方法。
(11)解析ConfigurationClassParser類的processConfigurationClass(ConfigurationClass configClass, Predicate
原始碼詳見:org.springframework.context.annotation.ConfigurationClassParser#processConfigurationClass(ConfigurationClass configClass, Predicate
protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {
//###############省略其他程式碼####################
SourceClass sourceClass = asSourceClass(configClass, filter);
do {
sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
}
while (sourceClass != null);
this.configurationClasses.put(configClass, configClass);
}
可以看到,在processConfigurationClass()方法中,會透過do-while()迴圈獲取配置類和其父類的註解資訊,SourceClass類中會封裝配置類上註解的詳細資訊。在在processConfigurationClass()方法中,呼叫了doProcessConfigurationClass()方法。
(12)解析ConfigurationClassParser類的doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass, Predicate
原始碼詳見:org.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass, Predicate
protected final SourceClass doProcessConfigurationClass(
ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
throws IOException {
//#############省略其他程式碼#############
// Process any @Import annotations
processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
//#############省略其他程式碼#############
// No superclass -> processing is complete
return null;
}
可以看到,在doProcessConfigurationClass()方法中,會呼叫processImports()方法來解析@Import註解。
(13)解析ConfigurationClassParser類的processImports(ConfigurationClass configClass, SourceClass currentSourceClass, Collection
原始碼詳見:org.springframework.context.annotation.ConfigurationClassParser#processImports(ConfigurationClass configClass, SourceClass currentSourceClass, Collection
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,
boolean checkForCircularImports) {
//################省略其他程式碼#################
this.importStack.push(configClass);
try {
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 = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class, this.environment, this.resourceLoader, this.registry);
Predicate<String> selectorFilter = selector.getExclusionFilter();
if (selectorFilter != null) {
exclusionFilter = exclusionFilter.or(selectorFilter);
}
if (selector instanceof DeferredImportSelector) {
this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
}
else {
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, 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 =
ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
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), exclusionFilter);
}
}
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to process import candidates for configuration class [" +
configClass.getMetadata().getClassName() + "]: " + ex.getMessage(), ex);
}
finally {
this.importStack.pop();
}
}
在processImports()方法中,如果使用@Import註解引入的是實現了ImportSelector介面的類,則執行的是if (candidate.isAssignable(ImportSelector.class))
條件的邏輯。如果@Import註解引入的是實現了ImportBeanDefinitionRegistrar介面的類,則執行的是 else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class))
條件的邏輯,否則執行的是else
條件的邏輯。
其中,執行if (candidate.isAssignable(ImportSelector.class))
條件的邏輯時,會呼叫ImportSelector介面的selectImports()方法獲取要注入到IOC容器中的Bean名稱陣列,如下所示。
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
呼叫ImportSelector介面的selectImports()方法時,就會呼叫案例程式中的MyImportSelector類的selectImports()方法。
(14)解析MyImportSelector類的selectImports(AnnotationMetadata importingClassMetadata)方法
原始碼詳見:io.binghe.spring.annotation.chapter05.selector.MyImportSelector#selectImports(AnnotationMetadata importingClassMetadata)
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{ImportSelectorBean.class.getName()};
}
可以看到,在MyImportSelector類的selectImports()方法中,會返回包含ImportSelectorBean類的全類名的String陣列,後續會將ImportSelectorBean類的Bean物件注入IOC容器。
(15)回到ConfigurationClassParser類的processImports(ConfigurationClass configClass, SourceClass currentSourceClass, Collection
如果@Import註解引入的是實現了ImportBeanDefinitionRegistrar介面的類,則執行的是 else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class))
條件的邏輯,如下所示。
else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
Class<?> candidateClass = candidate.loadClass();
ImportBeanDefinitionRegistrar registrar =
ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class, this.environment, this.resourceLoader, this.registry);
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
}
可以看到,在上述程式碼邏輯中會呼叫configClass的addImportBeanDefinitionRegistrar()方法來新增ImportBeanDefinitionRegistrar物件。
(16)解析ConfigurationClass類的addImportBeanDefinitionRegistrar(ImportBeanDefinitionRegistrar registrar, AnnotationMetadata importingClassMetadata)方法
原始碼詳見:org.springframework.context.annotation.ConfigurationClass#addImportBeanDefinitionRegistrar(ImportBeanDefinitionRegistrar registrar, AnnotationMetadata importingClassMetadata)。
void addImportBeanDefinitionRegistrar(ImportBeanDefinitionRegistrar registrar, AnnotationMetadata importingClassMetadata) {
this.importBeanDefinitionRegistrars.put(registrar, importingClassMetadata);
}
可以看到,在addImportBeanDefinitionRegistrar()方法中,會將傳入的registrar引數作為Key,importingClassMetadata引數作為Value儲存importBeanDefinitionRegistrars結構中。
其中,importBeanDefinitionRegistrars結構的定義如下所示。
private final Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> importBeanDefinitionRegistrars = new LinkedHashMap<>();
可以看到,importBeanDefinitionRegistrars是一個LinkedHashMap物件,也就是說,會將ImportBeanDefinitionRegistrar物件和AnnotationMetadata物件的對映關係存入一個LinkedHashMap物件中。
(17)再次回到ConfigurationClassParser類的processImports(ConfigurationClass configClass, SourceClass currentSourceClass, Collection
如果@Import註解引入的類既沒有實現ImportSelector介面,又沒有實現ImportBeanDefinitionRegistrar介面,則執行else
邏輯,如下所示。
else {
this.importStack.registerImport(
currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
}
在else
邏輯中,會按照解析@Configuration註解的邏輯執行,有關@Configuration註解的執行流程,可以參見第1章的內容,這裡不再贅述。
其實,在processImports()方法中,如果@Import註解引入的類實現了ImportSelector介面,並且沒有實現DeferredImportSelector介面的話,最終還是會執行processImports()方法的else
邏輯。
(18)回到ConfigurationClassPostProcessor類的processConfigBeanDefinitions(BeanDefinitionRegistry registry)方法。
在ConfigurationClassPostProcessor類的processConfigBeanDefinitions()方法中,執行完ConfigurationClassParser類的parse()方法後,會執行ConfigurationClassBeanDefinitionReader類的loadBeanDefinitions()方法,如下所示。
this.reader.loadBeanDefinitions(configClasses);
(19)解析ConfigurationClassBeanDefinitionReader類的loadBeanDefinitions(Set
原始碼詳見:org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitions(Set
public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
for (ConfigurationClass configClass : configurationModel) {
loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
}
}
可以看到,在loadBeanDefinitions()方法中,會迴圈遍歷傳入的configurationModel集合,並呼叫loadBeanDefinitionsForConfigurationClass()方法處理遍歷的每個元素。
(20)解析ConfigurationClassBeanDefinitionReader類的loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator)方法
原始碼詳見:org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator)。
private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
//################省略其他程式碼######################
if (configClass.isImported()) {
registerBeanDefinitionForImportedConfigurationClass(configClass);
}
for (BeanMethod beanMethod : configClass.getBeanMethods()) {
loadBeanDefinitionsForBeanMethod(beanMethod);
}
loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}
在loadBeanDefinitionsForConfigurationClass()方法中,如果@Import註解引入的是普通的類,或者是實現了ImportSelector介面的類,則會執行if (configClass.isImported())
條件的邏輯,此時會呼叫registerBeanDefinitionForImportedConfigurationClass()方法向IOC容器中注入配置類的BeanDefinition資訊。
(21)解析ConfigurationClassBeanDefinitionReader類的registerBeanDefinitionForImportedConfigurationClass(ConfigurationClass configClass)方法
原始碼詳見:org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#registerBeanDefinitionForImportedConfigurationClass(ConfigurationClass configClass),如下所示。
private void registerBeanDefinitionForImportedConfigurationClass(ConfigurationClass configClass) {
//###############省略其他程式碼#################
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(configBeanDef, configBeanName);
definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
this.registry.registerBeanDefinition(definitionHolder.getBeanName(),definitionHolder.getBeanDefinition());
configClass.setBeanName(configBeanName);
//###############省略其他程式碼#################
}
可以看到,在registerBeanDefinitionForImportedConfigurationClass()方法中會呼叫DefaultListableBeanFactory類的registerBeanDefinition()方法向IOC容器中注入BeanDefinition資訊。最終,會將BeanDefinition資訊儲存到DefaultListableBeanFactory類的beanDefinitionMap中。
(22)回到ConfigurationClassBeanDefinitionReader類的loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator)方法
如果@Import註解引入的是實現了ImportBeanDefinitionRegistrar介面的類,則在loadBeanDefinitionsForConfigurationClass()方法中呼叫loadBeanDefinitionsForConfigurationClass()方法時,會透過configClass的getImportBeanDefinitionRegistrars()方法獲取第(16)步儲存資訊的LinkedHashMap物件。
(23)解析ConfigurationClass類的getImportBeanDefinitionRegistrars()方法
原始碼詳見:org.springframework.context.annotation.ConfigurationClass#getImportBeanDefinitionRegistrars()。
Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> getImportBeanDefinitionRegistrars() {
return this.importBeanDefinitionRegistrars;
}
(24)再次ConfigurationClassBeanDefinitionReader類的loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator)方法。
在loadBeanDefinitionsForConfigurationClass()會呼叫loadBeanDefinitionsFromRegistrars()方法從實現了ImportBeanDefinitionRegistrar介面的類中載入Bean定義資訊。
(25)解析ConfigurationClassBeanDefinitionReader類的loadBeanDefinitionsFromRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars)方法
原始碼詳見:org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsFromRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars)。
private void loadBeanDefinitionsFromRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars) {
registrars.forEach((registrar, metadata) -> registrar.registerBeanDefinitions(metadata, this.registry, this.importBeanNameGenerator));
}
可以看到,在loadBeanDefinitionsFromRegistrars()方法中,會遍歷傳入的registrars,並呼叫每個registrar的registerBeanDefinitions()方法註冊BeanDefinition資訊。
(26)解析ImportBeanDefinitionRegistrar介面的registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,BeanNameGenerator importBeanNameGenerator)方法
原始碼詳見:org.springframework.context.annotation.ImportBeanDefinitionRegistrar#registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,BeanNameGenerator importBeanNameGenerator)
default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,BeanNameGenerator importBeanNameGenerator) {
registerBeanDefinitions(importingClassMetadata, registry);
}
可以看到,registerBeanDefinitions()方法是ImportBeanDefinitionRegistrar介面的一個預設方法,並在方法中呼叫了另一個registerBeanDefinitions()方法。其中,呼叫的這個registerBeanDefinitions()方法就是我們自己寫的案例中實現了ImportBeanDefinitionRegistrar介面的MyImportBeanDefinitionRegistrar類中的方法。
(27)解析MyImportBeanDefinitionRegistrar類的registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry)方法
原始碼詳見:io.binghe.spring.annotation.chapter05.registrar.MyImportBeanDefinitionRegistrar#registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry)。
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
String beanName = ImportBeanDefinitionRegistrarBean.class.getName();
BeanDefinition beanDefinition = new RootBeanDefinition(ImportBeanDefinitionRegistrarBean.class);
registry.registerBeanDefinition(beanName, beanDefinition);
}
可以看到,在registerBeanDefinitions()方法中,最終會呼叫DefaultListableBeanFactory類的registerBeanDefinition()方法向IOC容器中注入BeanDefinition資訊。最終,會將BeanDefinition資訊儲存到DefaultListableBeanFactory類的beanDefinitionMap中。
至此,整個@Import註解在Spring原始碼層面的執行流程分析完畢。
六、總結
@Import註解講完了,我們一起總結下吧!
本章,首先介紹了@Import註解的原始碼和使用場景,並列舉了使用@Import註解向IOC容器中注入Bean物件的三種案例。接下來,詳細分析了@Import註解的原始碼時序圖和@Import註解在Spring原始碼層面的執行流程。
七、思考
既然學完了,就開始思考幾個問題吧?
關於@Import註解,通常會有如下幾個經典面試題:
1.在ConfigurationClassParser類的processImports()中,如果@Import註解引入的是普通類或者引入的是實現了ImportSelector介面,並且沒有實現DeferredImportSelector介面的類,最終還是會執行processImports()方法的else
邏輯。那麼,如果@Import註解引入的是實現了ImportSelector介面,並且沒有實現DeferredImportSelector介面的類,最終是如何執行else
邏輯的?
2.@Import註解的三種案例在Spring底層的原始碼執行流程分別是什麼?
3.使用@Import註解向IOC容器中注入Bean與使用@Bean註解有什麼區別?
4.在你自己負責的專案中,會在哪些場景下使用@Import註解向IOC容器中注入Bean?
5.你從Spring的@Import註解的設計中得到了哪些啟發?
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70024420/viewspace-2937157/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 《Spring核心技術》第6章:深度解析@PropertySource註解Spring
- Spring註解之@ImportSpringImport
- Spring中@Import註解的使用SpringImport
- Spring5原始碼深度分析(二)之理解@Conditional,@Import註解Spring原始碼Import
- Spring Boot 2.0深度實踐之核心技術篇Spring Boot
- [Spring 深度解析]第5章 Spring之DAOSpring
- @import註解Import
- Spring Boot核心技術Spring Boot
- Spring5原始碼深度解析(一)之理解Configuration註解Spring原始碼
- Spring Boot 自動配置之@Enable* 與@Import註解Spring BootImport
- 【spring 註解】第2篇:@ComponentScanSpring
- Spring Framework 元件註冊 之 @ImportSpringFramework元件Import
- 《spring原始碼解讀》 - IoC 之解析 import 標籤Spring原始碼Import
- 【spring 註解】第3篇:@Scope、@Lazy和@Conditional註解Spring
- Spring原始碼深度解析(郝佳)-學習-原始碼解析-基於註解注入(二)Spring原始碼
- 【Spring註解驅動開發】在@Import註解中使用ImportBeanDefinitionRegistrar向容器中註冊beanSpringImportBean
- 【Spring註解驅動開發】在@Import註解中使用ImportSelector介面匯入beanSpringImportBean
- spring4.1.8擴充套件實戰之八:Import註解Spring套件Import
- 智慧雲解析有哪些核心技術?
- 《深度學習Python》核心技術實戰深度學習Python
- 深度學習DeepLearning核心技術實戰深度學習
- 智慧語音技術的深度解析
- @Import與@ImportResource註解的解讀Import
- 【Spring註解驅動開發】使用@Import註解給容器中快速匯入一個元件SpringImport元件
- 深度強化學習核心技術實戰強化學習
- Spring @Profile註解使用和原始碼解析Spring原始碼
- 譯 - Spring 核心技術之 Spring 容器擴充套件點Spring套件
- 【Spring註解驅動開發】AOP核心類解析,這是最全的一篇了!!Spring
- 搞懂分散式技術14:Spring Boot使用註解整合Redis快取分散式Spring BootRedis快取
- Spring 原始碼(7)Spring的註解是如何解析的?Spring原始碼
- 深度解讀DBSCAN聚類演算法:技術與實戰全解析聚類演算法
- 深度學習、強化學習核心技術實戰深度學習強化學習
- 《深度學習DeepLearning核心技術實戰培訓班》深度學習
- Spring Boot 最核心的 25 個註解,都是乾貨!Spring Boot
- 深度解析Spring AI:請求與響應機制的核心邏輯SpringAI
- 深度解析混合開發技術成熟度曲線
- 數倉安全:資料脫敏技術深度解析
- JVM CPU Profiler技術原理及原始碼深度解析JVM原始碼