注意,看完這篇文章須要很長很長很長時間。。。php
準備工做
本文會分析Spring的IOC模塊的總體流程,分析過程須要使用一個簡單的demo工程來啓動Spring,demo工程我以備好,須要的童鞋自行在下方連接下載:node
1
|
https://github.com/shiyujun/spring-framework
|
Demo工程示例代碼
本文源碼分析基於Spring5.0.0,因此pom文件中引入5.0的依賴git
1 2 3 4 5 6 7
|
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.0.RELEASE</version> </dependency> </dependencies>
|
而後寫一個簡單的接口和實現類github
1 2 3 4 5 6 7 8 9
|
public interface IOCService { public String hollo(); }
public class IOCServiceImpl implements IOCService { public String hollo() { return "Hello,IOC"; } }
|
新建一個application-ioc.xmlweb
1 2 3 4 5 6 7
|
<?xml version="1.0" encoding="UTF-8" ?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd" default-autowire="byName">
<bean id="iocservice" class="cn.shiyujun.service.impl.IOCServiceImpl"/> </beans>
|
啓動Springspring
1 2 3 4 5 6 7
|
public class IOCDemo { public static void main (String args[]){ ApplicationContext context = new ClassPathXmlApplicationContext("classpath:application-ioc.xml"); IOCService iocService=context.getBean(IOCService.class); System.out.println(iocService.hollo()); } }
|
上方一個簡單的demo工程相信各位童鞋在剛剛學習Spring的時候就已經玩的特別6了。我就不詳細的說明了,直接開始看源碼吧數據庫
ClassPathXmlApplicationContext
背景調查
在文章開始的demo工程中,我選擇使用了一個xml文件來配置了接口和實現類之間的關係,而後使用了ClassPathXmlApplicationContext這個類來加載這個配置文件。如今咱們就先來看一下這個類究竟是個什麼東東
首先看一下繼承關係圖(只保留了跟本文相關的,省略了不少其餘的繼承關係)
編程
能夠看到左下角的就是咱們今天的主角ClassPathXmlApplicationContext、而後它的旁邊是一個同門師兄弟FileSystemXmlApplicationContext。看名字就能夠知道它們哥倆都是經過加載配置文件來啓動Spring的,只不過一個是從程序內加載一個是從系統內加載。設計模式
除了這兩個還有一個類AnnotationConfigApplicationContext比較值得咱們關注,這個類是用來處理註解式編程的。數組
而最上邊的ApplicationContext則是大名鼎鼎的Spring核心上下文了
源碼分析
看一下這個類的源代碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
public class ClassPathXmlApplicationContext extends AbstractXmlApplicationContext { |
能夠看到總體來看源碼比較簡單,只有setConfigLocations
和refresh
兩個方法沒有看到具體的實現。可是若是你由於這個而小巧了Spring那可就大錯特錯了,setConfigLocations
只是一個開胃小菜,refresh
纔是咱們本文的重點
setConfigLocations
setConfigLocations
方法的主要工做有兩個:建立環境對象ConfigurableEnvironment和處理ClassPathXmlApplicationContext傳入的字符串中的佔位符
跟着setConfigLocations
方法一直往下走
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
public void setConfigLocations(String... locations) { if (locations != null) { Assert.noNullElements(locations, "Config locations must not be null"); this.configLocations = new String[locations.length]; for (int i = 0; i < locations.length; i++) { |
這裏getEnironment()
就涉及到了建立環境變量相關的操做了
獲取環境變量
1 2 3 4 5 6
|
public ConfigurableEnvironment getEnvironment() { if (this.environment == null) { this.environment = createEnvironment(); } return this.environment; }
|
看一下ConfigurableEnvironment
這個接口的繼承圖(1張沒能截全,兩張一塊看)
這個接口比較重要的就是兩部份內容了,一個是設置Spring的環境就是咱們常常用的spring.profile配置。另外就是系統資源Property
接着看createEnvironment()
方法,發現它返回了一個StandardEnvironment
類,而這個類中的customizePropertySources
方法就會往資源列表中添加Java進程中的變量和系統的環境變量
1 2 3 4
|
protected void customizePropertySources(MutablePropertySources propertySources) { propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties())); propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment())); }
|
處理佔位符
再次回到 resolvePath
方法後跟進經過上方獲取的ConfigurableEnvironment
接口的resolveRequiredPlaceholders
方法,終點就是下方的這個方法。這個方法主要就是處理全部使用${}方式的佔位符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
|
protected String parseStringValue( String value, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) {
StringBuilder result = new StringBuilder(value); int startIndex = value.indexOf(this.placeholderPrefix); while (startIndex != -1) { int endIndex = findPlaceholderEndIndex(result, startIndex); if (endIndex != -1) { String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex); String originalPlaceholder = placeholder; if (!visitedPlaceholders.add(originalPlaceholder)) { throw new IllegalArgumentException( "Circular placeholder reference '" + originalPlaceholder + "' in property definitions"); } placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders); |
refresh
配置文件名稱解析完畢後,就到了最關鍵的一步refresh方法。這個方法,接下來會用超級長的篇幅來解析這個方法
先看一下這個方法裏大體內容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
|
public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { |
是否是看着有點懵,不要着急,一行一行往下看,不研究明白誓不罷休
1. synchronized
爲了不refresh()
還沒結束,再次發起啓動或者銷燬容器引發的衝突
2. prepareRefresh()
作一些準備工做,記錄容器的啓動時間、標記「已啓動」狀態、檢查環境變量等
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
protected void prepareRefresh() { this.startupDate = System.currentTimeMillis(); this.closed.set(false); this.active.set(true);
if (logger.isInfoEnabled()) { logger.info("Refreshing " + this); }
|
其中檢查環境變量的核心方法爲,簡單來講就是若是存在環境變量的value爲空的時候就拋異常,而後中止啓動Spring
1 2 3 4 5 6 7 8 9 10 11
|
public void validateRequiredProperties() { MissingRequiredPropertiesException ex = new MissingRequiredPropertiesException(); for (String key : this.requiredProperties) { if (this.getProperty(key) == null) { ex.addMissingRequiredProperty(key); } } if (!ex.getMissingRequiredProperties().isEmpty()) { throw ex; } }
|
基於這個特性咱們能夠作一些擴展,提早在集合requiredProperties
中放入咱們這個項目必須存在的一些環境變量。假說咱們的生產環境數據庫地址、用戶名和密碼都是使用環境變量的方式注入進去來代替測試環境的配置,那麼就能夠在這裏添加這個校驗,在程序剛啓動的時候就能發現問題
3. obtainFreshBeanFactory()
乍一看這個方法也沒幾行代碼,可是這個方法負責了BeanFactory的初始化、Bean的加載和註冊等事件
1 2 3 4 5 6 7 8 9 10 11 12
|
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() { |
BeanFactory
先看refreshBeanFactory()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
|
protected final void refreshBeanFactory() throws BeansException { |
這裏一開始就實例化了一個DefaultListableBeanFactory,先看一下這個類的繼承關係
能夠看到這個哥們的背景至關大,全部關於容器的接口、抽象類他都繼承了。再看他的方法
這方法簡直多的嚇人,妥妥的Spring家族超級富二代。看他的方法名稱相信就能夠猜出他大部分的功能了
BeanDefinition
在看loadBeanDefinitions()
這個方法以前,就必須瞭解一個東西了。那就是:BeanDefinition
咱們知道BeanFactory是一個Bean容器,而BeanDefinition就是Bean的一種形式(它裏面包含了Bean指向的類、是否單例、是否懶加載、Bean的依賴關係等相關的屬性)。BeanFactory中就是保存的BeanDefinition。
看BeanDefinition的接口定義
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
|
public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
|
讀取配置文件
如今能夠看loadBeanDefinitions()
方法了,這個方法會根據配置,加載各個 Bean,而後放到 BeanFactory 中
1 2 3 4 5 6 7 8 9 10 11 12
|
@Overrideprotected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException { |
1 2 3 4 5 6 7 8 9 10 11
|
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException { Resource[] configResources = getConfigResources(); if (configResources != null) { reader.loadBeanDefinitions(configResources); } String[] configLocations = getConfigLocations(); if (configLocations != null) { reader.loadBeanDefinitions(configLocations); } }
|
第一個if是看有沒有系統指定的配置文件,若是沒有的話就走第二個if加載咱們最開始傳入的classpath:application-ioc.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
|
public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException { Assert.notNull(resources, "Resource array must not be null"); int counter = 0; |
離解析愈來愈近了
這裏先小小的看一下Spring中的設計模式,咱們跟着loadBeanDefinitions()
方法往下走,最終會進入類XmlBeanDefinitionReader,這是由於咱們這裏要解析的配置文件是XML。若是咱們使用Java類配置或者是Groovy的話就是另外的類了。看一下這個類繼承圖:
接着看
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
|
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException { Assert.notNull(encodedResource, "EncodedResource must not be null"); if (logger.isInfoEnabled()) { logger.info("Loading XML bean definitions from " + encodedResource.getResource()); }
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get(); if (currentResources == null) { currentResources = new HashSet<>(4); this.resourcesCurrentlyBeingLoaded.set(currentResources); } if (!currentResources.add(encodedResource)) { throw new BeanDefinitionStoreException( "Detected cyclic loading of " + encodedResource + " - check your import definitions!"); } try { |
下面是分爲兩步
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
|
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException { try { |
文件轉換就不詳細展開了,接着往下看
註冊Bean
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
|
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException { |
改變bean定義的擴展點
preProcessXml和postProcessXml着兩個辦法是留給咱們實現DefaultBeanDefinitionDocumentReader方法後自定義實現的
解析XML
接下來,看核心解析方法 parseBeanDefinitions()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
|
接着往下看這些標籤的處理方式
default標籤處理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) { if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) { |
簡單看一下 標籤的處理方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) { |
先從第一行往下看
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
|
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele) { return parseBeanDefinitionElement(ele, null) |
建立BeanDefinition
接着是最重要的地方,如何根據配置建立 BeanDefinition 實例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
|
public AbstractBeanDefinition parseBeanDefinitionElement( Element ele, String beanName, BeanDefinition containingBean) {
this.parseState.push(new BeanEntry(beanName));
String className = null; if (ele.hasAttribute(CLASS_ATTRIBUTE)) { className = ele.getAttribute(CLASS_ATTRIBUTE).trim(); }
try { String parent = null; if (ele.hasAttribute(PARENT_ATTRIBUTE)) { parent = ele.getAttribute(PARENT_ATTRIBUTE); } |
終於終於這麼長時間把這個BeanDefinition搞出來了,太不容易了!!!
接着回到剛纔的代碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) { |
Bean的註冊
此次看註冊bean的實現
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
public static void registerBeanDefinition( BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) throws BeanDefinitionStoreException {
String beanName = definitionHolder.getBeanName() |
又是一個長方法。。。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
|
|
到這裏已經初始化了 Bean 容器,的配置也相應的轉換爲了一個個BeanDefinition,而後註冊了全部的BeanDefinition到beanDefinitionMap
4. prepareBeanFactory()
如今回到最開始的refresh()
方法
prepareBeanFactory()
這個方法主要會設置BeanFactory的類加載器、添加幾個 BeanPostProcessor、手動註冊幾個特殊的bean
繼續擼代碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
|
protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
// 設置爲加載當前ApplicationContext類的類加載器 beanFactory.setBeanClassLoader(getClassLoader());
// 設置 BeanExpressionResolver beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver(beanFactory.getBeanClassLoader()));
beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment())) |
5. postProcessBeanFactory()
這個比較簡單,又是Spring的一個擴展點
若是有Bean實現了BeanFactoryPostProcessor接口,
那麼在容器初始化之後,Spring 會負責調用裏面的 postProcessBeanFactory 方法。具體的子類能夠在這步的時候添加一些特殊的 BeanFactoryPostProcessor 的實現類或作點什麼事
6. invokeBeanFactoryPostProcessors()
調用 BeanFactoryPostProcessor 各個實現類的 postProcessBeanFactory(factory) 方法
7. registerBeanPostProcessors()
又是一個擴展點
註冊 BeanPostProcessor 的實現類,注意不是BeanFactoryPostProcessor
此接口有兩個方法: postProcessBeforeInitialization 和 postProcessAfterInitialization
分別會在Bean初始化以前和初始化以後獲得執行
8. initMessageSource()
初始化當前 ApplicationContext 的 MessageSource,有想了解國際化的相關知識能夠深刻研究一下
9. initApplicationEventMulticaster()
這個方法主要爲初始化當前 ApplicationContext 的事件廣播器
擼代碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
private void initApplicationEventMulticaster() throws BeansException { |
10. onRefresh()
又是一個擴展點,子類能夠在這裏來搞事情
11. registerListeners()
註冊事件監聽器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
protected void registerListeners() { |
看到這裏不要覺得文章結束了,上方那麼大的篇幅其實總結起來僅僅只介紹了Bean容器的建立過程,因爲平臺的字數限制因此本篇文章只能寫到這裏了。後續內容請看明天的下篇