探索SpringBoot-一塊兒來看看Spring容器加載核心源碼(六)

前文回顧

上篇寫了探索SpringBoot-結合idea搭建Maven工程 續(五),好了,基本上關於Idea這塊暫時先告一個段落了。下面正式來探索下Spring Boot相關的內容。java

探索Spring 對象工廠能力

Spring2.x時代

learn-spring-framework-2.x中的pom文件中,加上最新的spring-context依賴。表示在這個模塊中,咱們使用的依賴是spring2.xspring

<dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>2.0.8</version>
        </dependency>
複製代碼

由於spring2.x時,必須須要編寫xml文件來加載spring上下文。因此,咱們除了啓動類以後,還打算編寫三個xml文件。bash

  • configurable-context.xml:表示須要加載的上下文
  • dev-context.xml:表示dev環境下須要加載的Bean
  • prod-context.xml:表示prod環境下須要加載的Bean

configurable-context.xml中,咱們使用import和Java的System Property來引入不一樣環境所須要的上下文。ide

<import resource="classpath:/META-INF/${env}-context.xml"/>
複製代碼

dev-context.xmlprod-context.xml中分別引入相同名稱的Bean,可是這個Bean的存在不一樣的屬性值。函數

dev-context.xmlpost

<!-- dev 環境 value Bean 定義-->
    <bean id="name" class="java.lang.String">
        <constructor-arg>
            <value>shane</value>
        </constructor-arg>
    </bean>
複製代碼

prod-context.xml學習

<!-- prod 環境 name Bean 定義-->
    <bean id="name" class="java.lang.String">
        <constructor-arg>
            <value>微秒</value>
        </constructor-arg>
    </bean>
複製代碼

最後定義啓動類,啓動類顯示加載Spring上下文,並輸出idnameBean的屬性值。固然會根據System Property的內容來動態加載不一樣環境下的Bean,而且輸出不一樣的值。this

這麼作,也是爲了演示Spring最最基礎的功能,做爲一個對象工廠的能力。idea

ConfigurableApplicationContextBootstrap.javaspa

public class ConfigurableApplicationContextBootstrap {

    static {
        // 調整系統屬性 "env",實現 "name" bean 的定義切換
        // envValue 可能來自於 "-D" 命令行啓動參數
        // 參數當不存在時,使用 "prod" 做爲默認值
        String envValue = System.getProperty("env", "dev");
        System.setProperty("env", envValue);
    }

    public static void main(String[] args) {
        // 定義 XML ApplicationContext
        // 先留意下這個location的方式,不須要寫classpath的前綴
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("META-INF/configurable-context.xml");
        // "name" bean 對象
        String value = (String) context.getBean("name");
        // "name" bean 內容輸出
        System.out.println("Bean 'name' 的內容爲:" + value);
        // 關閉上下文
        context.close();
    }
複製代碼

控制檯輸出

思考下Spring在這個過程當中,作了什麼事情呢?

  1. 加載xml文件,初始化Spring上下文
  2. 獲取Spring上下文中的對象

ClassPathXmlAppliationContext源碼初步分析

咱們進入到ClassPathXmlApplicationContext(String)構造函數中,能夠發現調用了另一個構造函數ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)並且設置了refresh參數爲trueparent參數爲null

/** * Create a new ClassPathXmlApplicationContext, loading the definitions * from the given XML file and automatically refreshing the context. * @param configLocation resource location * @throws BeansException if context creation failed */
	public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
		this(new String[] {configLocation}, true, null);
	}
	
	
	/** * Create a new ClassPathXmlApplicationContext with the given parent, * loading the definitions from the given XML files. * @param configLocations array of resource locations * @param refresh whether to automatically refresh the context, * loading all bean definitions and creating all singletons. * Alternatively, call refresh manually after further configuring the context. * @param parent the parent context * @throws BeansException if context creation failed * @see #refresh() */
	public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException {

		super(parent);
		setConfigLocations(configLocations);
		if (refresh) {
			refresh();
		}
	}
複製代碼

經過註釋能夠知道構造函數的做用是用給定的parent建立一個ClassPathXmlApplicationContext,而且根據XML文件加載定義的對象

  • configLocations: 資源文件的地址
  • refresh:是否自動刷新上下文,加載全部的bean定義而且建立全部的單例對象
  • parent:父類上下文(暫時不理解也沒有關係,Spring是存在父子上下文的,以後有機會講到)

refresh函數

構造函數首先解析了資源文件並設置爲上下文的一個屬性,以後進入到了關鍵的refresh函數中。

public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			// Prepare this context for refreshing. [1.爲refresh準備上下文]
			prepareRefresh();

			// Tell the subclass to refresh the internal bean factory.[2.告知子類refresh內部bean工廠]
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			// Prepare the bean factory for use in this context.[3.準備須要在本次上文中使用的bean工廠]
			prepareBeanFactory(beanFactory);

			try {
				// Allows post-processing of the bean factory in context subclasses.[4.容許上下文子類的bean工廠調用初始化函數]
				postProcessBeanFactory(beanFactory);

				// Invoke factory processors registered as beans in the context.[5.調用在上下文的註冊的工廠處理器]
				invokeBeanFactoryPostProcessors(beanFactory);

				// Register bean processors that intercept bean creation.[6.註冊在bean建立的過程當中的攔截處理器]
				registerBeanPostProcessors(beanFactory);

				// Initialize message source for this context.[7.初始化消息源]
				initMessageSource();

				// Initialize event multicaster for this context.[8.初始化事件多播機制]
				initApplicationEventMulticaster();

				// Initialize other special beans in specific context subclasses.[9.在這個特定的上下文子類中初始化其餘特殊的beans]
				onRefresh();

				// Check for listener beans and register them.[10.檢查監聽器的beans而且註冊他們]
				registerListeners();

				// Instantiate all remaining (non-lazy-init) singletons.[11.初始化存在的單例,不包括懶加載的對象]
				finishBeanFactoryInitialization(beanFactory);

				// Last step: publish corresponding event.[12.最後一步:發佈相關的事件]
				finishRefresh();
			}

			catch (BeansException ex) {
				// Destroy already created singletons to avoid dangling resources.
				beanFactory.destroySingletons();

				// Reset 'active' flag.
				cancelRefresh(ex);

				// Propagate exception to caller.
				throw ex;
			}
		}
	}
複製代碼

個人中文註釋,總共有12步核心步驟來初始化ClassPathXmlApplicationContext。下面咱們在一步步來分析。

prepareRefresh函數

首先看prepareRefresh()函數。

/** * Prepare this context for refreshing, setting its startup date and * active flag. */
	protected void prepareRefresh() {
		this.startupDate = System.currentTimeMillis();

		synchronized (this.activeMonitor) {
			this.active = true;
		}

		if (logger.isInfoEnabled()) {
			logger.info("Refreshing " + this);
		}
	}
複製代碼

能夠看到就只有兩步核心操做。首先記錄了當前的時間,而後嘗試獲取activeMonitor的鎖。能夠activeMonitor的做用後面聯繫起來再分析。

下一篇分析obtainFreshBeanFactory()函數,一步步來,畢竟是探索系列嘛,不知道的內容,不斷地探索,咱們才能將其轉換爲咱們知道的東西,這就是學習

固然這是Spring部分的核心的源代碼,不過由於SpringBoot實際上是構建在Spring基礎之上的,因此Spring的部分源代碼也會有講解。

關於寫做

之後這裏天天都會寫一篇文章,題材不限,內容不限,字數不限。儘可能把本身天天的思考都放入其中。

若是這篇文章給你帶來了幫助,能請你寫下是哪一個部分嗎?有效的反饋是對我最大的幫助。

我是shane。今天是2019年8月11日。百天寫做計劃的第十八天,18/100。

相關文章
相關標籤/搜索