我的架構夢:(五)Spring框架中的IoC容器源碼分析(上)

一、Spring IoC基礎

1、bean與BeanFactory的關係
在這裏插入圖片描述

2、BeanFactory與ApplicationContext區別

BeanFactorySpring框架中IoC容器的頂層接口,它只是用來定義一些基礎功能,定義一些基礎規範,而 ApplicationContext是它的一個子接口,所以ApplicationContext是具備BeanFactory提供的全部功能的。

通常,我們稱BeanFactorySpring IoC的基礎容器,ApplicationContext是容器的高級接口,比 BeanFactory要擁有更多的功能,比如說國際化支持資源訪問(xmljava配置類)等等。

在這裏插入圖片描述

3、啓動 IoC 容器的方式

3.1 Java環境下啓動IoC容器

  • ClassPathXmlApplicationContext:從類的根路徑下加載配置文件(推薦使用)
  • FileSystemXmlApplicationContext:從磁盤路徑上加載配置文件
  • AnnotationConfigApplicationContext:純註解模式下啓動Spring容器

3.2 Web環境下啓動IoC容器

  • 從xml啓動容器

    <!DOCTYPE web-app PUBLIC
     "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
     "http://java.sun.com/dtd/web-app_2_3.dtd" >
    
    <web-app>
      <display-name>Archetype Created Web Application</display-name>
    
      <!--配置Spring ioc容器的配置文件-->
      <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext.xml</param-value>
      </context-param>
      <!--使用監聽器啓動Spring的IOC容器-->
      <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
      </listener>
    </web-app>
    
  • 從配置類啓動容器

    <!DOCTYPE web-app PUBLIC
     "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
     "http://java.sun.com/dtd/web-app_2_3.dtd" >
    
    <web-app>
      <display-name>Archetype Created Web Application</display-name>
    
      <!--告訴ContextloaderListener知道我們使用註解的方式啓動ioc容器-->
      <context-param>
        <param-name>contextClass</param-name>
        <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
      </context-param>
      <!--配置啓動類的全限定類名-->
      <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>com.riemann.SpringConfig</param-value>
      </context-param>
      <!--使用監聽器啓動Spring的IOC容器-->
      <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
      </listener>
    </web-app>
    

二、Spring IoC高級特性

1、lazy-Init 延遲加載

1.1 Bean的延遲加載(延遲創建)

ApplicationContext 容器的默認行爲是在啓動服務器時將所有 singleton bean 提前進行實例化。提前實例化意味着作爲初始化過程的一部分,ApplicationContext 實例會創建並配置所有的singleton bean

比如:

<bean id="testBean" class="com.riemann.LazyBean" />
該bean默認的設置爲:
<bean id="testBean" calss="com.riemann.LazyBean" lazy-init="false" />

lazy-init="false",立即加載,表示在spring啓動時,立刻進行實例化。
如果不想讓一個singleton beanApplicationContext實現初始化時被提前實例化,那麼可以將bean
設置爲延遲實例化。

<bean id="testBean" calss="com.riemann.LazyBean" lazy-init="true" />

設置 lazy-inittruebean 將不會在 ApplicationContext 啓動時提前被實例化,而是第一次向容器
通過 getBean 索取 bean 時實例化的。

如果一個 beanscope 屬性爲 scope="pototype" 時,即使設置了 lazy-init="false",容器啓動時也不會實例化bean,而是調用 getBean 方法實例化的。

1.2 應用場景

  • 開啓延遲加載一定程度提高容器啓動和運轉性能
  • 對於不常使用的 Bean 設置延遲加載,這樣偶爾使用的時候再加載,不必要從一開始該 Bean佔用資源

2、FactoryBean 和 BeanFactory

BeanFactory接口是容器的頂級接口,定義了容器的一些基礎行爲,負責生產和管理Bean的一個工廠,具體使用它下面的子接口類型,比如ApplicationContext

此處我們重點分析FactoryBean

SpringBean有兩種,一種是普通Bean,一種是工廠Bean(FactoryBean)FactoryBean可以生成
某一個類型的Bean實例(返回給我們),也就是說我們可以藉助於它自定義Bean的創建過程

Bean創建的三種方式中的靜態方法實例化方法FactoryBean作用類似,FactoryBean使用較多,尤
其在Spring框架一些組件中會使用,還有其他框架和Spring框架整合時使用。

我們下面來看個例子:

FactoryBean接口:

// 可以讓我們自定義Bean的創建過程(完成複雜Bean的定義) 
public interface FactoryBean<T> {

	@Nullable
	// 返回FactoryBean創建的Bean實例,如果isSingleton返回true,則該實例會放到Spring容器 的單例對象緩存池中Map
	T getObject() throws Exception;
	
	@Nullable
	// 返回FactoryBean創建的Bean類型 
	Class<?> getObjectType();
	
	// 返回作用域是否單例
	default boolean isSingleton() {
		return true;
	}
	
}

Company類:

@Data
public class Company {

    private String name;
    private String address;
    private int scale;
    
}

CompanyFactoryBean類:

public class CompanyFactoryBean implements FactoryBean<Company> {

	private String companyInfo; // 公司名稱,地址,規模
	
	public void setCompanyInfo(String companyInfo) { 
		this.companyInfo = companyInfo;
	}
	
	@Override
	public Company getObject() throws Exception {
		// 模擬創建複雜對象Company
		Company company = new Company();
		String[] strings = companyInfo.split(","); 
		company.setName(strings[0]); 
		company.setAddress(strings[1]); 
		company.setScale(Integer.parseInt(strings[2]));
	    return company;
	}
	
	@Override
	public Class<?> getObjectType() { 
		return Company.class;
	}
	
	@Override
	public boolean isSingleton() {
		return true;
	} 
	
}

xml配置:

<bean id="companyBean" class="com.riemann.factory.CompanyFactoryBean"> 
	<property name="companyInfo" value="騰訊,南山科技園,60000"/>
</bean>

測試,獲取FactoryBean產生的對象:

Object companyBean = applicationContext.getBean("companyBean"); System.out.println("bean:" + companyBean);
// 結果如下
bean:Company{name='騰訊', address='南山科技園', scale=60000}

測試,獲取FactoryBean,需要在id之前添加“&”:

Object companyBean = applicationContext.getBean("&companyBean"); 
System.out.println("bean:" + companyBean);
// 結果如下 
bean:com.riemann.factory.CompanyFactoryBean@53f6fd09

3、後置處理器

Spring提供了兩種後處理bean的擴展接口,分別爲 BeanPostProcessor
BeanFactoryPostProcessor,兩者在使用上是有所區別的。

工廠初始化(BeanFactory)—> Bean對象

  • BeanFactory初始化之後可以使用BeanFactoryPostProcessor進行後置處理做一些事情
  • Bean對象實例化(並不是Bean的整個生命週期完成)之後可以使用BeanPostProcessor進行後置處理做一些事情

注意:對象不一定是springbean,而springbean一定是個對象

3.1 BeanPostProcessor

BeanPostProcessor是針對Bean級別的處理,可以針對某個具體的Bean。

public interface BeanPostProcessor {

	@Nullable
	default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}

	@Nullable
	default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}

}

該接口提供了兩個方法,分別在Bean的初始化方法前和初始化方法後執行,具體這個初始化方法指的是 什麼方法,類似我們在定義bean時,定義了init-method所指定的方法。

定義一個類實現了BeanPostProcessor,默認是會對整個Spring容器中所有的bean進行處理。如果要對具體的某個bean處理,可以通過方法參數判斷,兩個類型參數分別爲ObjectString,第一個參數是每個bean的實例,第二個參數是每個beanname或者id屬性的值。所以我們可以通過第二個參數,來判斷我們將要處理的具體的bean

注意:處理是發生在Spring容器的實例化和依賴注入之後

3.2 BeanFactoryPostProcessor

BeanFactory級別的處理,是針對整個Bean的工廠進行處理,典型應用:PropertyPlaceholderConfigurer

@FunctionalInterface
public interface BeanFactoryPostProcessor {

	void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;

}

此接口只提供了一個方法,方法參數爲ConfigurableListableBeanFactory,該參數類型定義了一些方法:

在這裏插入圖片描述

其中有個方法名爲getBeanDefinition的方法,我們可以根據此方法,找到我們定義beanBeanDefinition對象。然後我們可以對定義的屬性進行修改,以下是BeanDefinition中的方法:

在這裏插入圖片描述

方法名字類似我們bean標籤的屬性,setBeanClassName對應bean標籤中的class屬性,所以當我們拿到BeanDefinition對象時,我們可以手動修改bean標籤中所定義的屬性值。

BeanDefinition對象:我們在 XML 中定義的 bean標籤,Spring 解析 bean 標籤成爲一個 JavaBean, 這個JavaBean 就是 BeanDefinition

注意:調用 BeanFactoryPostProcessor 方法時,這時候bean還沒有實例化,此時 bean 剛被解析成 BeanDefinition對象。

三、Spring IOC 源碼深度剖析

1、Spring IoC容器初始化主體流程

1.1 Spring IoC的容器體系

IoC容器是Spring的核心模塊,是抽象了對象管理、依賴關係管理的框架解決方案。Spring 提供了很多的容器,其中 BeanFactory頂層容器(根容器),不能被實例化,它定義了所有 IoC 容器必須遵從的一套原則,具體的容器實現可以增加額外的功能,比如我們常用到的ApplicationContext,其下更具體的實現如 ClassPathXmlApplicationContext 包含了解析 xml 等一系列的內容,AnnotationConfigApplicationContext 則是包含了註解解析等一系列的內容。Spring IoC 容器繼承體系非常聰明,需要使用哪個層次用哪個層次即可,不必使用功能大而全的。

BeanFactory 頂級接口方法棧如下:

在這裏插入圖片描述

BeanFactory 容器繼承體系如下:
在這裏插入圖片描述

通過其接口設計,我們可以看到我們一貫使用的 ApplicationContext 除了繼承BeanFactory的子接口, 還繼承了ResourceLoaderMessageSource等接口,因此其提供的功能也就更豐富了。

下面我們以 ClasspathXmlApplicationContext 爲例,深入源碼說明 IoC 容器的初始化流程。

1.2 Bean生命週期關鍵時機點

思路:創建一個類 RiemannBean ,讓其實現幾個特殊的接口,並分別在接口實現的構造器接口方法中 斷點,觀察線程調用棧,分析出 Bean 對象創建和管理關鍵點的觸發時機。

RiemannBean

public class RiemannBean implements InitializingBean, ApplicationContextAware {

	private ItBean itBean;

	public void setItBean(ItBean itBean) {
		this.itBean = itBean;
	}

	public RiemannBean() {
		System.out.println("RiemannBean 構造器...");
	}

	@Override
	public void afterPropertiesSet() throws Exception {
		System.out.println("RiemannBean afterPropertiesSet...");
	}

	public void print() {
		System.out.println("RiemannBean print方法業務邏輯執行...");
	}

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		System.out.println("RiemannBean setApplicationContext...");
	}

}

BeanPostProcessor 接口實現類

public class MyBeanPostProcessor implements BeanPostProcessor {

	public MyBeanPostProcessor() {
		System.out.println("BeanPostProcessor 實現類構造函數...");
	}

	@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		if("riemannBean".equals(beanName)) {
			System.out.println("BeanPostProcessor 實現類 postProcessBeforeInitialization 方法被調用中...");
		}
		return bean;
	}

	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		if("riemannBean".equals(beanName)) {
			System.out.println("BeanPostProcessor 實現類 postProcessAfterInitialization 方法被調用中...");
		}
		return bean;
	}

}

BeanFactoryPostProcessor 接口實現類

public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

	public MyBeanFactoryPostProcessor() {
		System.out.println("BeanFactoryPostProcessor的實現類構造函數...");
	}

	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
		System.out.println("BeanFactoryPostProcessor的實現方法調用中...");
	}

}

applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	   xsi:schemaLocation="
	    http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
">

	
	<bean id="riemannBean" class="com.riemann.RiemannBean">
		<property name="ItBean" ref="itBean"/>
	</bean>
	<bean id="myBeanFactoryPostProcessor" class="com.riemann.MyBeanFactoryPostProcessor"/>
	<bean id="myBeanPostProcessor" class="com.riemann.MyBeanPostProcessor"/>
	<!--循環依賴問題-->
	<bean id="itBean" class="com.riemann.ItBean">
		<property name="riemannBean" ref="riemannBean"/>
	</bean>

</beans>

IoC 容器源碼分析用例

public class IocTest {

	/**
	 *  Ioc 容器源碼分析基礎案例
	 */
	@Test
	public void testIoC() {
		// ApplicationContext是容器的高級接口,BeanFacotry(頂級容器/根容器,規範了/定義了容器的基礎行爲)
		// Spring應用上下文,官方稱之爲 IoC容器(錯誤的認識:容器就是map而已;準確來說,map是ioc容器的一個成員,
		// 叫做單例池, singletonObjects,容器是一組組件和過程的集合,包括BeanFactory、單例池、BeanPostProcessor等以及之間的協作流程)

		/**
		 * Ioc容器創建管理Bean對象的,Spring Bean是有生命週期的
		 * 構造器執行、初始化方法執行、Bean後置處理器的before/after方法:AbstractApplicationContext#refresh#finishBeanFactoryInitialization
		 * Bean工廠後置處理器初始化、方法執行:AbstractApplicationContext#refresh#invokeBeanFactoryPostProcessors
		 * Bean後置處理器初始化:AbstractApplicationContext#refresh#registerBeanPostProcessors
		 */
		ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
		RiemannBean riemannBean = applicationContext.getBean(RiemannBean.class);
		System.out.println(riemannBean);
	}

}

上面註釋是我已經斷點調試完驗證的結果

1.3 小結

根據上面的調試分析,我們發現Bean對象創建的幾個關鍵時機點代碼層級的調用都在 AbstractApplicationContext 類 的 refresh 方法中,可⻅這個方法對於Spring IoC 容器初始化來說相當關鍵,彙總如下:

關鍵點 觸發代碼
構造器 AbstractApplicationContext#refresh#finishBeanFactoryInitialization(beanFactory)
BeanFactoryPostProcessor 初始化 AbstractApplicationContext#refresh#invokeBeanFactoryPostProcessors(beanFactory)
BeanFactoryPostProcessor 方法調用 AbstractApplicationContext#refresh#invokeBeanFactoryPostProcessors(beanFactory)
BeanPostProcessor 初始化 AbstractApplicationContext#refresh#registerBeanPostProcessors(beanFactory)
BeanPostProcessor 方法調用 AbstractApplicationContext#refresh#finishBeanFactoryInitialization(beanFactory)

2、Spring IoC容器初始化主流程源碼剖析

由上分析可知,Spring IoC 容器初始化的關鍵環節就在 AbstractApplicationContext#refresh() 方法中 ,我們查看 refresh 方法來俯瞰容器創建的主體流程,主體流程下的具體子流程我們後面再來討論。

@Override
public void refresh() throws BeansException, IllegalStateException {
	/**
	 * 對象鎖加鎖
	 */
	synchronized (this.startupShutdownMonitor) {
		// Prepare this context for refreshing.
		/**
		 * 刷新前的預處理
		 * 表示在真正做refresh操作之前需要準備做的事情:
		 *    設置Spring容器啓動時間
		 *    開啓活躍狀態,撤銷關閉狀態
		 *    驗證環境信息裏一些必須存在的屬性
		 */
		prepareRefresh();

		// Tell the subclass to refresh the internal bean factory.
		/**
		 * 獲取BeanFactory:默認實現是DefaultListableBeanFactory
		 * Bean獲取並封裝成BeanDefinition對象
		 * 加載BeanDefinition 並註冊到BeanDefinitionRegistry
		 */
		ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

		// Prepare the bean factory for use in this context.
		/**
		 * 獲取BeanFactory的準備工作:BeanFactory進行一些設置,比如context的類加載器等。
		 */
		prepareBeanFactory(beanFactory);

		try {
			// Allows post-processing of the bean factory in context subclasses.
			/**
			 * BeanFactory準備工作完成後進行的後置處理工作
			 */
			postProcessBeanFactory(beanFactory);

			// Invoke factory processors registered as beans in the context.
			/**
			 * 實例化實現了BeanFactoryPostProcessor接口的Bean,並調用接口方法
			 */
			invokeBeanFactoryPostProcessors(beanFactory);

			// Register bean processors that intercept bean creation.
			/**
			 * 註冊BeanPostProcessor(Bean的後置處理器),在創建bean的前後等執行
			 */
			registerBeanPostProcessors(beanFactory);

			// Initialize message source for this context.
			/**
			 * 初始化MessageSource組件(做國際化功能、消息綁定、消息解析)
			 */
			initMessageSource();

			// Initialize event multicaster for this context.
			/**
			 * 初始化事件派發器
			 */
			initApplicationEventMulticaster();

			// Initialize other special beans in specific context subclasses.
			/**
			 * 子類重寫這個方法,在容器刷新的時候可以自定義邏輯,如創建Tomcat、Jetty等WEB服務器
			 */
			onRefresh();

			// Check for listener beans and register them.
			/**
			 * 註冊應用的監聽器,就是註冊實現了ApplicationListener接口的監聽器bean
			 */
			registerListeners();

			// Instantiate all remaining (non-lazy-init) singletons.
			/**
			 * 初始化所有剩下的非懶加載的單例bean
			 * 初始化創建非懶加載方式的單例Bean實例(未設置屬性)
			 * 填充屬性
			 * 初始化方法調用(比如調用afterPropertiesSet方法、init-method方法)
			 * 調用BeanPostProcessor(Bean的後置處理器)對實例bean進行後置處理
			 */
			finishBeanFactoryInitialization(beanFactory);

			// Last step: publish corresponding event.
			/**
			 * 完成context的刷新,主要是調用LifecycleProcessor的onRefresh()方法,並且發佈事件(ContextRefreshEvent)
			 */
			finishRefresh();
		}

		catch (BeansException ex) {
			if (logger.isWarnEnabled()) {
				logger.warn("Exception encountered during context initialization - " +
						"cancelling refresh attempt: " + ex);
			}

			// Destroy already created singletons to avoid dangling resources.
			destroyBeans();

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

			// Propagate exception to caller.
			throw ex;
		}

		finally {
			// Reset common introspection caches in Spring's core, since we
			// might not ever need metadata for singleton beans anymore...
			resetCommonCaches();
		}
	}
}

下一篇:我的架構夢:(六)Spring框架中的IoC容器源碼分析(下)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章