詳實明瞭的IOC容器的介紹,啓動流程以及Bean的實例化和依賴注入

前言

今天我們來認識一下Spring IOC容器,本文主要介紹SpringIOC容器的核心要點以及其啓動流程和實例化流程。

項目環境

Springframework 4.3.12

核心要點

Spring IOC是什麼?他有什麼作用呢?我們通過了解學習,Spring IOC是一個容器,用於生成和管理Bean的實例,以及實例之間的依賴關係,然後注入相關的依賴。這裏我們可以把IOC容器想象成一個餐館。我們去餐館點菜的話,不需要關心菜的生成過程,不需要關心菜的原材料從哪裏來。我們只需要最終做好的菜。這裏的菜就是我們的需要的Bean。不同的菜對應不同的Bean。沒有IOC 容器的情況下,如果需要一個Bean的話,就需要自己來new一個對象的實例,比如A類依賴了B類,那麼就需要在A類中new一個B類的實例對象,這就好像我們要自己在家動手做菜一樣。有了IOC容器之後,如果A類依賴B類,只需要通過IOC容器幫我們創建A類的實例和B類的實例,然後IOC容器會將B類的實例注入到A類中。這就很像餐館把菜做好之後送給我們一樣。既然IOC容器這麼6,那麼他是如何實現這一切的呢?
還是回到餐館那個例子,做菜的話就需要與原材料和菜譜,同樣的IOC容器想要管理各個業務對象以及他們之間的依賴關係,就需要通過某種途徑來記錄和管理這些信息,而BeanDefinition對象就承擔了這個責任。IOC容器中每一個Bean都會有一個對應的BeanDefinition實例,該實例負責保存bean對象的所有必要信息,包括Bean對象的class類型,是否是抽象類,構造方法和參數,以及其他屬性等,這裏的BeanDefinition就相當於原材料。而BeanDefinitionRegistry對象和BeanFactory對象就相當於菜譜,告訴我們如何將原材料加工成相應的菜餚。
下面我們就來看看這些比較核心的類和接口。
在這裏插入圖片描述

作用
BeanFactory BeanFactory接口主要包括了getBean,containsBean,getAllases等方法,作用是操作Bean
BeanDefinitionRegistry BeanDefinitionRegistry接口抽象出了Bean的註冊邏輯,其主要包括了registerBeanDefinition,removeBeanDefinition,getBeanDefinition等方法
ConfigurableListableBeanFactory ConfigurableListableBeanFactory接口包括了getBeanDefinition等方法,可以獲取BeanDefinition實例
DefaultListableBeanFactory DefaultListableBeanFactory類同時實現了ConfigurableListableBeanFactory接口和BeanDefinitionRegistry接口,說明其承擔了Bean的註冊和管理工作
BeanDefinition BeanDefinition接口是用來存儲Bean對象的必要信息,包括Bean對象的class類型,是否是抽象類,構造方法和參數,依賴關係,以及其他屬性等
PropertyValue 這個類就是具體存放Bean對象的屬性信息以及其依賴信息

認識上面的幾個核心接口和類,對我們下面看Bean的啓動過程和實例化過程有很大的幫助。

需要說明的是,在Spring中,ApplicationContext是IOC容器的承載體,而BeanFactory是操作這個容器的工具,兩者關係緊密,相互協作,refresh方法實現了ApplicationContext和BeanFactory相互協作的過程,不同之處主要在於子類 AbstractRefreshableApplicationContext 和 GenericApplicationContext 中實現,兩者使用的 BeanFactory 都爲 DefaultListableBeanFactory,它構建在BeanFactory之 上,屬於更⾼級的容器,除了具有BeanFactory的所有能⼒之外,還提供對事件監聽機制以及國際化的⽀持等。它管理的bean,在容器啓動 時全部完成初始化和依賴注⼊操作。

IOC容器的啓動過程

介紹完了IOC容器的核心類和要點,接下來我們看看IOC容器的啓動過程,其啓動過程主要有如下三個步驟:

1. 資源定位,找到配置文件

這裏定位資源有兩種方式,一種是通過ClassPathXmlApplicationContext類來解析Spring的配置文件的形式,就是通過配置文件來定義Bean的情況,另外,一種情況就是通過註解的方式來定義Bean的情況,這種情況是通過AnnotationConfigApplicationContext類解析的,主要是掃描項目的classPath下定義的註解。下面我們首先介紹下通過ClassPathXmlApplicationContext。這個類的核心作用是作爲一個解析Xml的入口,其調用鏈是: ClassPathXmlApplicationContext類的構造器
------>AbstractApplicationContext類的refresh方法
----->調用AbstractRefreshableApplicationContext類的refreshBeanFactory方法
---->XmlWebApplicationContext類的loadBeanDefinitions方法
----> AbstractBeanDefinitionReader類的loadBeanDefinitions方法
---->XmlBeanDefinitionReader類的loadBeanDefinitions方法
---->XmlBeanDefinitionReader類的doLoadBeanDefinitions方法
---->XmlBeanDefinitionReader類的registerBeanDefinitions方法
---->DefaultBeanDefinitionDocumentReader類的registerBeanDefinitions方法
調用層次很深,我們就直接跳到核心的方法來看。下面我們就來看看registerBeanDefinitions方法

	@Override
	public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
		this.readerContext = readerContext;
		logger.debug("Loading bean definitions");
		//讀取XML文件
		Element root = doc.getDocumentElement();
		//載入並註冊BeanDefinition
		doRegisterBeanDefinitions(root);
	}

然後,registerBeanDefinitions方法只是讀取到根節點root之後,就另外一個核心方法doRegisterBeanDefinitions方法,然後,doRegisterBeanDefinitions方法又把邏輯轉給了parseBeanDefinitions方法,這個parseBeanDefinitions方法首先獲取所有的子節點 ,然後遍歷解析子節點,載入BeanDefinition又交給了parseDefaultElement方法和parseCustomElement方法。

	protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
		if (delegate.isDefaultNamespace(root)) {
			//獲取子節點
			NodeList nl = root.getChildNodes();
			for (int i = 0; i < nl.getLength(); i++) {
				Node node = nl.item(i);
				if (node instanceof Element) {
					Element ele = (Element) node;
					if (delegate.isDefaultNamespace(ele)) {
						//解析節點
						parseDefaultElement(ele, delegate);
					}
					else {
						delegate.parseCustomElement(ele);
					}
				}
			}
		}
		else {
			delegate.parseCustomElement(root);
		}
	}

2.BeanDefinition的載入和解析,將配置文件解析成BeanDefiniton

說完了配置文件的解析之後,接下來,我們來看看BeanDefinition的載入和解析。我們直接找到parseDefaultElement方法。

	private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
		//省略部分非核心代碼
		//如果節點是bean節點,說明是一個Bean
		else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
			processBeanDefinition(ele, delegate);
		}
	}

這個方法按照節點名,調用不同的處理方法,在此處我們只看節點爲bean時調用的方法processBeanDefinition方法。

	protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
		BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
		if (bdHolder != null) {
			bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
			try {
				// Register the final decorated instance.(註冊BeanDefinition)
				BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
			}
			catch (BeanDefinitionStoreException ex) {
				getReaderContext().error("Failed to register bean definition with name '" +
						bdHolder.getBeanName() + "'", ele, ex);
			}
			// Send registration event.
			getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
		}
	}

我們重點看BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());這個方法,這個方法纔是真正的將傳入BeanDefinitionRegistry類,載入並解析BeanDefinition,然後對BeanDefinition進行註冊。

3. BeanDefinition的註冊,將BeanDefinition向Map中註冊beanDefinitionMap

接下來就到了我們的重頭戲,註冊BeanDefinition到beanDefinitionMap中,其中key就是Bean的id,其中beanDefinitionMap是一個定義在DefaultListableBeanFactory類中全局的線程安全的map,用於存放解析到的BeanDefinition

	private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>(256);

讓我們來看看registerBeanDefinition方法吧,這個方法核心的步驟有兩步:

  1. 根據傳入的beanName檢查beanDefinition是否存在,如果存在就是一系列的校驗,主要是保證BeanDefinition的單例性,就是說IOC容器中每個Bean的實例時單例的。
  2. 將傳入的beanDefinition實例放到beanDefinitionMap中。
	public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
			throws BeanDefinitionStoreException {
				if (hasBeanCreationStarted()) {
				// Cannot modify startup-time collection elements anymore (for stable iteration)
				//加鎖,保證線程安全
				synchronized (this.beanDefinitionMap) {
				// 將beanDefinition值設置到beanDefinitionMap中
					this.beanDefinitionMap.put(beanName, beanDefinition);
					List<String> updatedDefinitions = new ArrayList<String>(this.beanDefinitionNames.size() + 1);
					updatedDefinitions.addAll(this.beanDefinitionNames);
					updatedDefinitions.add(beanName);
					this.beanDefinitionNames = updatedDefinitions;
					if (this.manualSingletonNames.contains(beanName)) {
						Set<String> updatedSingletons = new LinkedHashSet<String>(this.manualSingletonNames);
						updatedSingletons.remove(beanName);
						this.manualSingletonNames = updatedSingletons;
					}
				}
			}
			else {
				// Still in startup registration phase,將beanDefinition值設置到beanDefinitionMap中
				this.beanDefinitionMap.put(beanName, beanDefinition);
				this.beanDefinitionNames.add(beanName);
				this.manualSingletonNames.remove(beanName);
			}
			this.frozenBeanDefinitionNames = null;
			}

小結,至此,我們對IOC容器的初始化過程就解析完了,其實其初始化過程還是比較簡單的,只是Spring的代碼結構比較深,核心代碼不好找。

Bean的實例化和依賴注入

說完了IOC容器的初始化過程,接下來,我們來看看IOC容器的實例化過程。經過上一個階段,所有Bean定義都通過BeanDefinition的方式註冊到了BeanDefinitionRegistry中。當某個請求通過容器的getBean方法請求某個對象,或者因爲依賴關係容器需要隱式的調用getBean方法時,就會觸發第二階段的活動,IOC容器首先檢查所請求的對象是否已經實例化完成,如果沒有,則會根據註冊的BeanDefinition所提供的信息實例化請求對象。併爲其注入依賴,當該對象裝配完成後,容器會立即返回給請求方法。

Bean的實例化

讓我們從前面提到的getBean方法說起,這裏的調用鏈如下:
AbstractBeanFactory類的getBean方法
----->AbstractBeanFactory類的doGetBean方法
----->AbstractBeanFactory類的createBean方法
這裏的createBean方法就是我們實例化Bean的核心方法。讓我們來看看。

	protected Object createBean(String beanName, RootBeanDefinition mbd, Object[] args) throws BeanCreationException {
				//獲取BeanDefinition的實現類RootBeanDefinition實例
				RootBeanDefinition mbdToUse = mbd;
				//獲取Bean的Class對象,用於後面生成實例
				Class<?> resolvedClass = resolveBeanClass(mbd, beanName);
		if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) {
			mbdToUse = new RootBeanDefinition(mbd);
			mbdToUse.setBeanClass(resolvedClass);
		}
		//創造一個Bean的實例
		Object beanInstance = doCreateBean(beanName, mbdToUse, args);
			if (bean != null) {
				return bean;
			}
	}

後面的調用鏈是
AbstractAutowireCapableBeanFactory類的doCreateBean方法
------->createBeanInstance方法,
------>createBeanInstance方法,
----->instantiateBean方法,
----->SimpleInstantiationStrategy類的instantiate方法.
這個instantiate方法就是包含了實例化Bean的真正邏輯

@Override
	public Object instantiate(RootBeanDefinition bd, String beanName, BeanFactory owner) {
		//省略部分代碼
		// Don't override the class with CGLIB if no overrides.
		if (bd.getMethodOverrides().isEmpty()) {
			//通過反射的方式創造Bean		
			return BeanUtils.instantiateClass(constructorToUse);
		}
		else {
			// Must generate CGLIB subclass.(通過CGLIB的方式生成Bean)
			return instantiateWithMethodInjection(bd, beanName, owner);
		}
	}

看到instantiate方法我們就明白了,其實就是拿到RootBeanDefinition保存的class等信息通過反射或者CCLIB的方式來生成Bean的實例。

Bean的依賴注入

說完了Bean的實例化,接下來我們來說下Bean的依賴注入。依賴注入必須用到PropertyValue類,這個類保存了Bean的所有屬性和依賴信息。
依賴注入的調用流程是AbstractAutowireCapableBeanFactory類的applyBeanPropertyValues方法調用了applyPropertyValues方法。
讓我們來看看applyPropertyValues方法。

	protected void applyPropertyValues(String beanName, BeanDefinition mbd, BeanWrapper bw, PropertyValues pvs) {
			//類型轉換接口
			TypeConverter converter = getCustomTypeConverter();
		if (converter == null) {
			converter = bw;
		}
		BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(this, beanName, mbd, converter);

	String propertyName = pv.getName();
		                //對於ref來說就是beanName,對於value 來說就是value
				Object originalValue = pv.getValue();
				Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue);
				boolean convertible = bw.isWritableProperty(propertyName) &&
						!PropertyAccessorUtils.isNestedOrIndexedProperty(propertyName);
				if (convertible) {
					convertedValue = convertForProperty(resolvedValue, propertyName, bw, converter);
				}
	try {
			//設置依賴屬性
			bw.setPropertyValues(new MutablePropertyValues(deepCopy));
		}
		catch (BeansException ex) {
			throw new BeanCreationException(
					mbd.getResourceDescription(), beanName, "Error setting property values", ex);
		}

	}
	
		private Object convertForProperty(Object value, String propertyName, BeanWrapper bw, TypeConverter converter) {
		if (converter instanceof BeanWrapperImpl) {
			return ((BeanWrapperImpl) converter).convertForProperty(value, propertyName);
		}
		else {
			PropertyDescriptor pd = bw.getPropertyDescriptor(propertyName);
			MethodParameter methodParam = BeanUtils.getWriteMethodParameter(pd);
			return converter.convertIfNecessary(value, pd.getPropertyType(), methodParam);
		}
	}

其中TypeConverter 類型轉化接口,將傳入的值轉化爲其需要的類型。
SimpleTypeCoverter 是TypeConverter接口的一個實現。其依賴於java.beans中的PropertyEditor,其類似於java GUI中的編程,例如:拖拽一個button, 然後,設置其顏色,長度,寬度,這些都屬於button的屬性,在java.beans中將這些抽象成了一個PropertyEditor 接口。 setAsText(), 例如button 的高度,值是什麼跟屬性的類型密切相關。

總結

本文主要介紹了IOC容器的核心概念,以及其啓動過程。然後,就是介紹了Bean的實例化過程,熟悉IOC容器我們需要先了解清楚其核心的幾個接口,例如:BeanFactory接口,BeanDefinitionRegistry接口等。IOC容器的啓動過程無非就是解析配置文件,將屬性值存放到BeanDefinition中。Bean的實例化是通過反射或者CGLIB的方式來的。Bean中的屬性是存放在PropertyValue中。

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