SpringBoot啟動流程分析(四):IoC容器的初始化過程

超級小小黑發表於2019-06-24

SpringBoot系列文章簡介

SpringBoot原始碼閱讀輔助篇:

  Spring IoC容器與應用上下文的設計與實現

SpringBoot啟動流程原始碼分析:

  1. SpringBoot啟動流程分析(一):SpringApplication類初始化過程
  2. SpringBoot啟動流程分析(二):SpringApplication的run方法
  3. SpringBoot啟動流程分析(三):SpringApplication的run方法之prepareContext()方法
  4. SpringBoot啟動流程分析(四):IoC容器的初始化過程
  5. SpringBoot啟動流程分析(五):SpringBoot自動裝配原理實現
  6. SpringBoot啟動流程分析(六):IoC容器依賴注入

筆者註釋版Spring Framework與SpringBoot原始碼git傳送門:請不要吝嗇小星星

  1. spring-framework-5.0.8.RELEASE
  2. SpringBoot-2.0.4.RELEASE

第五步:重新整理應用上下文

一、前言

  在前面的部落格中談到IoC容器的初始化過程,主要分下面三步:

1 BeanDefinition的Resource定位
2 BeanDefinition的載入
3 向IoC容器註冊BeanDefinition

  在上一篇文章介紹了prepareContext()方法,在準備重新整理階段做了什麼工作。本文我們主要從refresh()方法中總結IoC容器的初始化過程。
  從run方法的,refreshContext()方法一路跟下去,最終來到AbstractApplicationContext類的refresh()方法。

 1 @Override
 2 public void refresh() throws BeansException, IllegalStateException {
 3     synchronized (this.startupShutdownMonitor) {
 4         // Prepare this context for refreshing.
 5         //重新整理上下文環境
 6         prepareRefresh();
 7         // Tell the subclass to refresh the internal bean factory.
 8         //這裡是在子類中啟動 refreshBeanFactory() 的地方
 9         ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
10         // Prepare the bean factory for use in this context.
11         //準備bean工廠,以便在此上下文中使用
12         prepareBeanFactory(beanFactory);
13         try {
14             // Allows post-processing of the bean factory in context subclasses.
15             //設定 beanFactory 的後置處理
16             postProcessBeanFactory(beanFactory);
17             // Invoke factory processors registered as beans in the context.
18             //呼叫 BeanFactory 的後處理器,這些處理器是在Bean 定義中向容器註冊的
19             invokeBeanFactoryPostProcessors(beanFactory);
20             // Register bean processors that intercept bean creation.
21             //註冊Bean的後處理器,在Bean建立過程中呼叫
22             registerBeanPostProcessors(beanFactory);
23             // Initialize message source for this context.
24             //對上下文中的訊息源進行初始化
25             initMessageSource();
26             // Initialize event multicaster for this context.
27             //初始化上下文中的事件機制
28             initApplicationEventMulticaster();
29             // Initialize other special beans in specific context subclasses.
30             //初始化其他特殊的Bean
31             onRefresh();
32             // Check for listener beans and register them.
33             //檢查監聽Bean並且將這些監聽Bean向容器註冊
34             registerListeners();
35             // Instantiate all remaining (non-lazy-init) singletons.
36             //例項化所有的(non-lazy-init)單件
37             finishBeanFactoryInitialization(beanFactory);
38             // Last step: publish corresponding event.
39             //釋出容器事件,結束Refresh過程
40             finishRefresh();
41         } catch (BeansException ex) {
42             if (logger.isWarnEnabled()) {
43                 logger.warn("Exception encountered during context initialization - " +
44                         "cancelling refresh attempt: " + ex);
45             }
46             // Destroy already created singletons to avoid dangling resources.
47             destroyBeans();
48             // Reset 'active' flag.
49             cancelRefresh(ex);
50             // Propagate exception to caller.
51             throw ex;
52         } finally {
53             // Reset common introspection caches in Spring's core, since we
54             // might not ever need metadata for singleton beans anymore...
55             resetCommonCaches();
56         }
57     }
58 }

   從以上程式碼中我們可以看到,refresh()方法中所作的工作也挺多,我們沒辦法面面俱到,主要根據IoC容器的初始化步驟和IoC依賴注入的過程進行分析,圍繞以上兩個過程,我們主要介紹重要的方法,其他的請看註釋。

 

二、obtainFreshBeanFactory();

  在啟動流程的第三步:初始化應用上下文。中我們建立了應用的上下文,並觸發了GenericApplicationContext類的構造方法如下所示,建立了beanFactory,也就是建立了DefaultListableBeanFactory類。

1 public GenericApplicationContext() {
2     this.beanFactory = new DefaultListableBeanFactory();
3 }

  關於obtainFreshBeanFactory()方法,其實就是拿到我們之前建立的beanFactory。

 1 protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
 2     //重新整理BeanFactory
 3     refreshBeanFactory();
 4     //獲取beanFactory
 5     ConfigurableListableBeanFactory beanFactory = getBeanFactory();
 6     if (logger.isDebugEnabled()) {
 7         logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
 8     }
 9     return beanFactory;
10 }

  從上面程式碼可知,在該方法中主要做了三個工作,重新整理beanFactory,獲取beanFactory,返回beanFactory。

  首先看一下refreshBeanFactory()方法,跟下去來到GenericApplicationContext類的refreshBeanFactory()發現也沒做什麼。

1 @Override
2 protected final void refreshBeanFactory() throws IllegalStateException {
3     if (!this.refreshed.compareAndSet(false, true)) {
4         throw new IllegalStateException(
5                 "GenericApplicationContext does not support multiple refresh attempts: just call 'refresh' once");
6     }
7     this.beanFactory.setSerializationId(getId());
8 }
TIPS:
  1,AbstractApplicationContext類有兩個子類實現了refreshBeanFactory(),但是在前面第三步初始化上下文的時候,
例項化了GenericApplicationContext類,所以沒有進入AbstractRefreshableApplicationContext中的refreshBeanFactory()方法。
  2,this.refreshed.compareAndSet(false, true) 
  這行程式碼在這裡表示:GenericApplicationContext只允許重新整理一次   
  這行程式碼,很重要,不是在Spring中很重要,而是這行程式碼本身。首先看一下this.refreshed屬性: 
private final AtomicBoolean refreshed = new AtomicBoolean(); 
  java J.U.C併發包中很重要的一個原子類AtomicBoolean。通過該類的compareAndSet()方法可以實現一段程式碼絕對只實現一次的功能。
感興趣的自行百度吧。

 

三、prepareBeanFactory(beanFactory);

  從字面意思上可以看出準備BeanFactory。

  看程式碼,具體看看做了哪些準備工作。這個方法不是重點,看註釋吧。

 1 protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
 2     // Tell the internal bean factory to use the context's class loader etc.
 3     // 配置類載入器:預設使用當前上下文的類載入器
 4     beanFactory.setBeanClassLoader(getClassLoader());
 5     // 配置EL表示式:在Bean初始化完成,填充屬性的時候會用到
 6     beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver(beanFactory.getBeanClassLoader()));
 7     // 新增屬性編輯器 PropertyEditor
 8     beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment()));
 9 
10     // Configure the bean factory with context callbacks.
11     // 新增Bean的後置處理器
12     beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));
13     // 忽略裝配以下指定的類
14     beanFactory.ignoreDependencyInterface(EnvironmentAware.class);
15     beanFactory.ignoreDependencyInterface(EmbeddedValueResolverAware.class);
16     beanFactory.ignoreDependencyInterface(ResourceLoaderAware.class);
17     beanFactory.ignoreDependencyInterface(ApplicationEventPublisherAware.class);
18     beanFactory.ignoreDependencyInterface(MessageSourceAware.class);
19     beanFactory.ignoreDependencyInterface(ApplicationContextAware.class);
20 
21     // BeanFactory interface not registered as resolvable type in a plain factory.
22     // MessageSource registered (and found for autowiring) as a bean.
23     // 將以下類註冊到 beanFactory(DefaultListableBeanFactory) 的resolvableDependencies屬性中
24     beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory);
25     beanFactory.registerResolvableDependency(ResourceLoader.class, this);
26     beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this);
27     beanFactory.registerResolvableDependency(ApplicationContext.class, this);
28 
29     // Register early post-processor for detecting inner beans as ApplicationListeners.
30     // 將早期後處理器註冊為application監聽器,用於檢測內部bean
31     beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(this));
32 
33     // Detect a LoadTimeWeaver and prepare for weaving, if found.
34     //如果當前BeanFactory包含loadTimeWeaver Bean,說明存在類載入期織入AspectJ,
35     // 則把當前BeanFactory交給類載入期BeanPostProcessor實現類LoadTimeWeaverAwareProcessor來處理,
36     // 從而實現類載入期織入AspectJ的目的。
37     if (beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
38         beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
39         // Set a temporary ClassLoader for type matching.
40         beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
41     }
42 
43     // Register default environment beans.
44     // 將當前環境變數(environment) 註冊為單例bean
45     if (!beanFactory.containsLocalBean(ENVIRONMENT_BEAN_NAME)) {
46         beanFactory.registerSingleton(ENVIRONMENT_BEAN_NAME, getEnvironment());
47     }
48     // 將當前系統配置(systemProperties) 註冊為單例Bean
49     if (!beanFactory.containsLocalBean(SYSTEM_PROPERTIES_BEAN_NAME)) {
50         beanFactory.registerSingleton(SYSTEM_PROPERTIES_BEAN_NAME, getEnvironment().getSystemProperties());
51     }
52     // 將當前系統環境 (systemEnvironment) 註冊為單例Bean
53     if (!beanFactory.containsLocalBean(SYSTEM_ENVIRONMENT_BEAN_NAME)) {
54         beanFactory.registerSingleton(SYSTEM_ENVIRONMENT_BEAN_NAME, getEnvironment().getSystemEnvironment());
55     }
56 }

 

四、postProcessBeanFactory(beanFactory);

  postProcessBeanFactory()方法向上下文中新增了一系列的Bean的後置處理器。後置處理器工作的時機是在所有的beanDenifition載入完成之後,bean例項化之前執行。簡單來說Bean的後置處理器可以修改BeanDefinition的屬性資訊。

  關於這個方法就先這樣吧,有興趣的可以直接百度該方法。篇幅有限,對該方法不做過多介紹。

 

五、invokeBeanFactoryPostProcessors(beanFactory);(重點)

  上面說過,IoC容器的初始化過程包括三個步驟,在invokeBeanFactoryPostProcessors()方法中完成了IoC容器初始化過程的三個步驟。

  1,第一步:Resource定位

  在SpringBoot中,我們都知道他的包掃描是從主類所在的包開始掃描的,prepareContext()方法中,會先將主類解析成BeanDefinition,然後在refresh()方法的invokeBeanFactoryPostProcessors()方法中解析主類的BeanDefinition獲取basePackage的路徑。這樣就完成了定位的過程。其次SpringBoot的各種starter是通過SPI擴充套件機制實現的自動裝配,SpringBoot的自動裝配同樣也是在invokeBeanFactoryPostProcessors()方法中實現的。還有一種情況,在SpringBoot中有很多的@EnableXXX註解,細心點進去看的應該就知道其底層是@Import註解,在invokeBeanFactoryPostProcessors()方法中也實現了對該註解指定的配置類的定位載入。

  常規的在SpringBoot中有三種實現定位,第一個是主類所在包的,第二個是SPI擴充套件機制實現的自動裝配(比如各種starter),第三種就是@Import註解指定的類。(對於非常規的不說了)

  2,第二步:BeanDefinition的載入

  在第一步中說了三種Resource的定位情況,定位後緊接著就是BeanDefinition的分別載入。所謂的載入就是通過上面的定位得到的basePackage,SpringBoot會將該路徑拼接成:classpath*:org/springframework/boot/demo/**/*.class這樣的形式,然後一個叫做PathMatchingResourcePatternResolver的類會將該路徑下所有的.class檔案都載入進來,然後遍歷判斷是不是有@Component註解,如果有的話,就是我們要裝載的BeanDefinition。大致過程就是這樣的了。

TIPS:
    @Configuration,@Controller,@Service等註解底層都是@Component註解,只不過包裝了一層罷了。

  3、第三個過程:註冊BeanDefinition

   這個過程通過呼叫上文提到的BeanDefinitionRegister介面的實現來完成。這個註冊過程把載入過程中解析得到的BeanDefinition向IoC容器進行註冊。通過上文的分析,我們可以看到,在IoC容器中將BeanDefinition注入到一個ConcurrentHashMap中,IoC容器就是通過這個HashMap來持有這些BeanDefinition資料的。比如DefaultListableBeanFactory 中的beanDefinitionMap屬性。

  OK,總結完了,接下來我們通過程式碼看看具體是怎麼實現的。

 1 protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
 2     PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
 3     ...
 4 }
 5 // PostProcessorRegistrationDelegate類
 6 public static void invokeBeanFactoryPostProcessors(
 7         ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {
 8     ...
 9     invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
10     ...
11 }
12 // PostProcessorRegistrationDelegate類
13 private static void invokeBeanDefinitionRegistryPostProcessors(
14         Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors, BeanDefinitionRegistry registry) {
15 
16     for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {
17         postProcessor.postProcessBeanDefinitionRegistry(registry);
18     }
19 }
20 // ConfigurationClassPostProcessor類
21 @Override
22 public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
23     ...
24     processConfigBeanDefinitions(registry);
25 }
26 // ConfigurationClassPostProcessor類
27 public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
28     ...
29     do {
30         parser.parse(candidates);
31         parser.validate();
32         ...
33     }
34     ...
35 }

   一路跟蹤呼叫棧,來到ConfigurationClassParser類的parse()方法。

 1 // ConfigurationClassParser類
 2 public void parse(Set<BeanDefinitionHolder> configCandidates) {
 3     this.deferredImportSelectors = new LinkedList<>();
 4     for (BeanDefinitionHolder holder : configCandidates) {
 5         BeanDefinition bd = holder.getBeanDefinition();
 6         try {
 7             // 如果是SpringBoot專案進來的,bd其實就是前面主類封裝成的 AnnotatedGenericBeanDefinition(AnnotatedBeanDefinition介面的實現類)
 8             if (bd instanceof AnnotatedBeanDefinition) {
 9                 parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
10             } else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
11                 parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
12             } else {
13                 parse(bd.getBeanClassName(), holder.getBeanName());
14             }
15         } catch (BeanDefinitionStoreException ex) {
16             throw ex;
17         } catch (Throwable ex) {
18             throw new BeanDefinitionStoreException(
19                     "Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
20         }
21     }
22     // 載入預設的配置---》(對springboot專案來說這裡就是自動裝配的入口了)
23     processDeferredImportSelectors();
24 }

   看上面的註釋,在前面的prepareContext()方法中,我們詳細介紹了我們的主類是如何一步步的封裝成AnnotatedGenericBeanDefinition,並註冊進IoC容器的beanDefinitionMap中的。

TIPS:
  至於processDeferredImportSelectors();方法,後面我們分析SpringBoot的自動裝配的時候會詳細講解,各種starter是如何一步步的實現自動裝配的。<SpringBoot啟動流程分析(五):SpringBoot自動裝配原理實現>

 

  繼續沿著parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());方法跟下去

  1 // ConfigurationClassParser類
  2 protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
  3     processConfigurationClass(new ConfigurationClass(metadata, beanName));
  4 }
  5 // ConfigurationClassParser類
  6 protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
  7     ...
  8     // Recursively process the configuration class and its superclass hierarchy.
  9     //遞迴地處理配置類及其父類層次結構。
 10     SourceClass sourceClass = asSourceClass(configClass);
 11     do {
 12         //遞迴處理Bean,如果有父類,遞迴處理,直到頂層父類
 13         sourceClass = doProcessConfigurationClass(configClass, sourceClass);
 14     }
 15     while (sourceClass != null);
 16 
 17     this.configurationClasses.put(configClass, configClass);
 18 }
 19 // ConfigurationClassParser類
 20 protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
 21         throws IOException {
 22 
 23     // Recursively process any member (nested) classes first
 24     //首先遞迴處理內部類,(SpringBoot專案的主類一般沒有內部類)
 25     processMemberClasses(configClass, sourceClass);
 26 
 27     // Process any @PropertySource annotations
 28     // 針對 @PropertySource 註解的屬性配置處理
 29     for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
 30             sourceClass.getMetadata(), PropertySources.class,
 31             org.springframework.context.annotation.PropertySource.class)) {
 32         if (this.environment instanceof ConfigurableEnvironment) {
 33             processPropertySource(propertySource);
 34         } else {
 35             logger.warn("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
 36                     "]. Reason: Environment must implement ConfigurableEnvironment");
 37         }
 38     }
 39 
 40     // Process any @ComponentScan annotations
 41     // 根據 @ComponentScan 註解,掃描專案中的Bean(SpringBoot 啟動類上有該註解)
 42     Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
 43             sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
 44     if (!componentScans.isEmpty() &&
 45             !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
 46         for (AnnotationAttributes componentScan : componentScans) {
 47             // The config class is annotated with @ComponentScan -> perform the scan immediately
 48             // 立即執行掃描,(SpringBoot專案為什麼是從主類所在的包掃描,這就是關鍵了)
 49             Set<BeanDefinitionHolder> scannedBeanDefinitions =
 50                     this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
 51             // Check the set of scanned definitions for any further config classes and parse recursively if needed
 52             for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
 53                 BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
 54                 if (bdCand == null) {
 55                     bdCand = holder.getBeanDefinition();
 56                 }
 57                 // 檢查是否是ConfigurationClass(是否有configuration/component兩個註解),如果是,遞迴查詢該類相關聯的配置類。
 58                 // 所謂相關的配置類,比如@Configuration中的@Bean定義的bean。或者在有@Component註解的類上繼續存在@Import註解。
 59                 if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
 60                     parse(bdCand.getBeanClassName(), holder.getBeanName());
 61                 }
 62             }
 63         }
 64     }
 65 
 66     // Process any @Import annotations
 67     //遞迴處理 @Import 註解(SpringBoot專案中經常用的各種@Enable*** 註解基本都是封裝的@Import)
 68     processImports(configClass, sourceClass, getImports(sourceClass), true);
 69 
 70     // Process any @ImportResource annotations
 71     AnnotationAttributes importResource =
 72             AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
 73     if (importResource != null) {
 74         String[] resources = importResource.getStringArray("locations");
 75         Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
 76         for (String resource : resources) {
 77             String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
 78             configClass.addImportedResource(resolvedResource, readerClass);
 79         }
 80     }
 81 
 82     // Process individual @Bean methods
 83     Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
 84     for (MethodMetadata methodMetadata : beanMethods) {
 85         configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
 86     }
 87 
 88     // Process default methods on interfaces
 89     processInterfaces(configClass, sourceClass);
 90 
 91     // Process superclass, if any
 92     if (sourceClass.getMetadata().hasSuperClass()) {
 93         String superclass = sourceClass.getMetadata().getSuperClassName();
 94         if (superclass != null && !superclass.startsWith("java") &&
 95                 !this.knownSuperclasses.containsKey(superclass)) {
 96             this.knownSuperclasses.put(superclass, configClass);
 97             // Superclass found, return its annotation metadata and recurse
 98             return sourceClass.getSuperClass();
 99         }
100     }
101 
102     // No superclass -> processing is complete
103     return null;
104

  看doProcessConfigurationClass()方法。(SpringBoot的包掃描的入口方法,重點哦)

  我們先大致說一下這個方法裡面都幹了什麼,然後稍後再閱讀原始碼分析。

TIPS:
  在以上程式碼的第60行parse(bdCand.getBeanClassName(), holder.getBeanName());會進行遞迴呼叫,
因為當Spring掃描到需要載入的類會進一步判斷每一個類是否滿足是@Component/@Configuration註解的類,
如果滿足會遞迴呼叫parse()方法,查詢其相關的類。
  同樣的第68行processImports(configClass, sourceClass, getImports(sourceClass), true);
通過@Import註解查詢到的類同樣也會遞迴查詢其相關的類。
  兩個遞迴在debug的時候會很亂,用文字敘述起來更讓人難以理解,所以,我們只關注對主類的解析,及其類的掃描過程。

  上面程式碼的第29行 for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(... 獲取主類上的@PropertySource註解(關於該註解是怎麼用的請自行百度),解析該註解並將該註解指定的properties配置檔案中的值儲存到Spring的 Environment中,Environment介面提供方法去讀取配置檔案中的值,引數是properties檔案中定義的key值。

  42行 Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable( sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class); 解析主類上的@ComponentScan註解,呃,怎麼說呢,42行後面的程式碼將會解析該註解並進行包掃描。

  68行 processImports(configClass, sourceClass, getImports(sourceClass), true); 解析主類上的@Import註解,並載入該註解指定的配置類。

TIPS:

  在spring中好多註解都是一層一層封裝的,比如@EnableXXX,是對@Import註解的二次封裝。@SpringBootApplication註解=@ComponentScan+@EnableAutoConfiguration+@Import+@Configuration+@Component。@Controller,@Service等等是對@Component的二次封裝。。。

 

5.1、看看42-64行幹了啥

  從上面的42行往下看,來到第49行 Set<BeanDefinitionHolder> scannedBeanDefinitions = this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName()); 

  進入該方法

 1 // ComponentScanAnnotationParser類
 2 public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
 3     ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
 4             componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);
 5     ...
 6     // 根據 declaringClass (如果是SpringBoot專案,則引數為主類的全路徑名)
 7     if (basePackages.isEmpty()) {
 8         basePackages.add(ClassUtils.getPackageName(declaringClass));
 9     }
10     ...
11     // 根據basePackages掃描類
12     return scanner.doScan(StringUtils.toStringArray(basePackages));
13 }

  發現有兩行重要的程式碼

  為了驗證程式碼中的註釋,debug,看一下declaringClass,如下圖所示確實是我們的主類的全路徑名。

  跳過這一行,繼續debug,檢視basePackages,該set集合中只有一個,就是主類所在的路徑。

TIPS:
  為什麼只有一個還要用一個集合呢,因為我們也可以用@ComponentScan註解指定掃描路徑。

  到這裡呢IoC容器初始化三個步驟的第一步,Resource定位就完成了,成功定位到了主類所在的包。

  接著往下看 return scanner.doScan(StringUtils.toStringArray(basePackages)); Spring是如何進行類掃描的。進入doScan()方法。

 1 // ComponentScanAnnotationParser類
 2 protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
 3     Assert.notEmpty(basePackages, "At least one base package must be specified");
 4     Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
 5     for (String basePackage : basePackages) {
 6         // 從指定的包中掃描需要裝載的Bean
 7         Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
 8         for (BeanDefinition candidate : candidates) {
 9             ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
10             candidate.setScope(scopeMetadata.getScopeName());
11             String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
12             if (candidate instanceof AbstractBeanDefinition) {
13                 postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
14             }
15             if (candidate instanceof AnnotatedBeanDefinition) {
16                 AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
17             }
18             if (checkCandidate(beanName, candidate)) {
19                 BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
20                 definitionHolder =
21                         AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
22                 beanDefinitions.add(definitionHolder);
23                 //將該 Bean 註冊進 IoC容器(beanDefinitionMap)
24                 registerBeanDefinition(definitionHolder, this.registry);
25             }
26         }
27     }
28     return beanDefinitions;
29 }

  這個方法中有兩個比較重要的方法,第7行 Set<BeanDefinition> candidates = findCandidateComponents(basePackage); 從basePackage中掃描類並解析成BeanDefinition,拿到所有符合條件的類後在第24行 registerBeanDefinition(definitionHolder, this.registry); 將該類註冊進IoC容器。也就是說在這個方法中完成了IoC容器初始化過程的第二三步,BeanDefinition的載入,和BeanDefinition的註冊。

 

5.1.1、findCandidateComponents(basePackage);

  跟蹤呼叫棧

 1 // ClassPathScanningCandidateComponentProvider類
 2 public Set<BeanDefinition> findCandidateComponents(String basePackage) {
 3     ...
 4     else {
 5         return scanCandidateComponents(basePackage);
 6     }
 7 }
 8 // ClassPathScanningCandidateComponentProvider類
 9 private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
10     Set<BeanDefinition> candidates = new LinkedHashSet<>();
11     try {
12         //拼接掃描路徑,比如:classpath*:org/springframework/boot/demo/**/*.class
13         String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
14                 resolveBasePackage(basePackage) + '/' + this.resourcePattern;
15         //從 packageSearchPath 路徑中掃描所有的類
16         Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
17         boolean traceEnabled = logger.isTraceEnabled();
18         boolean debugEnabled = logger.isDebugEnabled();
19         for (Resource resource : resources) {
20             if (traceEnabled) {
21                 logger.trace("Scanning " + resource);
22             }
23             if (resource.isReadable()) {
24                 try {
25                     MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
26                     // //判斷該類是不是 @Component 註解標註的類,並且不是需要排除掉的類
27                     if (isCandidateComponent(metadataReader)) {
28                         //將該類封裝成 ScannedGenericBeanDefinition(BeanDefinition介面的實現類)類
29                         ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
30                         sbd.setResource(resource);
31                         sbd.setSource(resource);
32                         if (isCandidateComponent(sbd)) {
33                             if (debugEnabled) {
34                                 logger.debug("Identified candidate component class: " + resource);
35                             }
36                             candidates.add(sbd);
37                         } else {
38                             if (debugEnabled) {
39                                 logger.debug("Ignored because not a concrete top-level class: " + resource);
40                             }
41                         }
42                     } else {
43                         if (traceEnabled) {
44                             logger.trace("Ignored because not matching any filter: " + resource);
45                         }
46                     }
47                 } catch (Throwable ex) {
48                     throw new BeanDefinitionStoreException(
49                             "Failed to read candidate component class: " + resource, ex);
50                 }
51             } else {
52                 if (traceEnabled) {
53                     logger.trace("Ignored because not readable: " + resource);
54                 }
55             }
56         }
57     } catch (IOException ex) {
58         throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
59     }
60     return candidates;
61 }

  在第13行將basePackage拼接成classpath*:org/springframework/boot/demo/**/*.class,在第16行的getResources(packageSearchPath);方法中掃描到了該路徑下的所有的類。然後遍歷這些Resources,在第27行判斷該類是不是 @Component 註解標註的類,並且不是需要排除掉的類。在第29行將掃描到的類,解析成ScannedGenericBeanDefinition,該類是BeanDefinition介面的實現類。OK,IoC容器的BeanDefinition載入到這裡就結束了。

  回到前面的doScan()方法,debug看一下結果(截圖中所示的就是我定位的需要交給Spring容器管理的類)。

 

5.1.2、registerBeanDefinition(definitionHolder, this.registry);

  檢視registerBeanDefinition()方法。是不是有點眼熟,在前面介紹prepareContext()方法時,我們詳細介紹了主類的BeanDefinition是怎麼一步一步的註冊進DefaultListableBeanFactory的beanDefinitionMap中的。在此呢我們就省略1w字吧。完成了BeanDefinition的註冊,就完成了IoC容器的初始化過程。此時,在使用的IoC容器DefaultListableFactory中已經建立了整個Bean的配置資訊,而這些BeanDefinition已經可以被容器使用了。他們都在BeanbefinitionMap裡被檢索和使用。容器的作用就是對這些資訊進行處理和維護。這些資訊是容器簡歷依賴反轉的基礎。

1 protected void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) {
2     BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, registry);
3 }

   OK,到這裡IoC容器的初始化過程的三個步驟就梳理完了。當然這只是針對SpringBoot的包掃描的定位方式的BeanDefinition的定位,載入,和註冊過程。前面我們說過,還有兩種方式@Import和SPI擴充套件實現的starter的自動裝配。

 

5.2、@Import註解的解析過程

  相信不說大家也應該知道了,各種@EnableXXX註解,很大一部分都是對@Import的二次封裝(其實也是為了解耦,比如當@Import匯入的類發生變化時,我們的業務系統也不需要改任何程式碼)。

  呃,我們又要回到上文中的ConfigurationClassParser類的doProcessConfigurationClass方法的第68行processImports(configClass, sourceClass, getImports(sourceClass), true);,跳躍性比較大。上面解釋過,我們只針對主類進行分析,因為這裡有遞迴。

  processImports(configClass, sourceClass, getImports(sourceClass), true);中configClass和sourceClass引數都是主類相對應的哦。

TIPS:
  在分析這一塊的時候,我在主類上加了@EnableCaching註解。

  首先看getImports(sourceClass);

1 private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException {
2     Set<SourceClass> imports = new LinkedHashSet<>();
3     Set<SourceClass> visited = new LinkedHashSet<>();
4     collectImports(sourceClass, imports, visited);
5     return imports;
6 }

   debug

  正是@EnableCaching註解中的@Import註解指定的類。另外兩個呢是主類上的@SpringBootApplication中的@Import註解指定的類。不信你可以一層層的剝開@SpringBootApplication註解的皮去一探究竟。

  至於processImports()方法,大家自行debug吧,相信看到這裡,思路大家都已經很清楚了。

  

  凌晨兩點了,睡覺,明天繼續上班。

 

 

  原創不易,轉載請註明出處。

  如有錯誤的地方還請留言指正。

 

相關文章