SpringBoot系列文章簡介
SpringBoot原始碼閱讀輔助篇:
SpringBoot啟動流程原始碼分析:
- SpringBoot啟動流程分析(一):SpringApplication類初始化過程
- SpringBoot啟動流程分析(二):SpringApplication的run方法
- SpringBoot啟動流程分析(三):SpringApplication的run方法之prepareContext()方法
- SpringBoot啟動流程分析(四):IoC容器的初始化過程
- SpringBoot啟動流程分析(五):SpringBoot自動裝配原理實現
- SpringBoot啟動流程分析(六):IoC容器依賴注入
筆者註釋版Spring Framework與SpringBoot原始碼git傳送門:請不要吝嗇小星星
第五步:重新整理應用上下文
一、前言
在前面的部落格中談到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吧,相信看到這裡,思路大家都已經很清楚了。
凌晨兩點了,睡覺,明天繼續上班。
原創不易,轉載請註明出處。
如有錯誤的地方還請留言指正。