1. 程式人生 > 程式設計 >探索SpringBoot-一起來看看Spring容器載入核心原始碼(六)

探索SpringBoot-一起來看看Spring容器載入核心原始碼(六)

前文回顧

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

探索Spring 物件工廠能力

Spring2.x時代

learn-spring-framework-2.x中的pom檔案中,加上最新的spring-context依賴。表示在這個模組中,我們使用的依賴是spring2.x

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

因為spring2.x時,必須需要編寫xml檔案來載入spring上下文。所以,我們除了啟動類之後,還打算編寫三個xml檔案。

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

configurable-context.xml中,我們使用import和Java的System Property來引入不同環境所需要的上下文。

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

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

dev-context.xml

    <!-- 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,並且輸出不同的值。

這麼做,也是為了演示Spring最最基礎的功能,作為一個物件工廠的能力。

ConfigurableApplicationContextBootstrap.java

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。