1. 程式人生 > >Spring4.x原始碼解析:IOC容器底層原理解析

Spring4.x原始碼解析:IOC容器底層原理解析

引包

 		<dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>4.3.7.RELEASE</version>
        </dependency>

IOC簡單介紹

1、作用

Spring IOC 主要負責建立物件,解決物件之間的依賴,並且管理這些物件的整個生命週期。

2、體系結構

IOC容器是由三部分組成

  • BeanDefinition(Bean定義)
  • BeanDefinition解析器
  • Bean管理

BeanDefinition

BeanDefinition:org.springframework.beans.factory.config.BeanDefinition

XML配置檔案裡的一個標籤(這裡就以<bean>標籤為例)對應一個BeanDefinition

	<bean id="" name="" class="" init-method=""  scope="" lazy-init="default" destroy-method="" >
		<property name=
"" ref=""></property> </bean>

1、常用的屬性

  • id:在IOC容器唯一標識,如果id重複,後面id的bean會覆蓋前面id的bean。
  • class:bean的class路徑
  • init-method:初始化方法,建立Bean後呼叫
  • scope:物件作用域,singleton(單例)、prototype(多例)。預設singleton
  • lazy-init:是否懶載入,使用該Bean的使用在初始化。false(否)、true(是),預設false
  • destroy-method:銷燬方法,銷燬Bean後呼叫。
  • property:需要注入的屬性
  • name:bean的alias;如果沒有配置id,那麼id = name;如果id存在,那麼name只是一個alias;

2、儲存方式

  • xml

BeanDefinition解析器

BeanDefinitionReader:org.springframework.beans.factory.support.BeanDefinitionReader

當我們在xml定義了許多的<bean>標籤,需要一個工具去統一解析這些<bean>標籤,把這些<bean>標籤轉換成BeanDefinition,那麼這就是BeanDefinitionReader的工作

BeanDefinitionReader主要處理流程:

  1. 載入xml檔案
  2. 把xml檔案轉換成Document
  3. 解析Document成BeanDefinition
  4. 把BeanDefinition註冊到BeanDefinitionRegistry

Bean管理

當我們通過BeanDefinitionReader把所有的配置解析成一系列的BeanDefinition並且註冊到BeanDefinitionRegistry,那麼就需要一個容器,通過這些BeanDefinition去生成Bean,並且對Bean管理,那麼就是BeanFactory。

Spring我們常用的是DefaultListableBeanFactory:org.springframework.beans.factory.support.DefaultListableBeanFactory

主要作用

  1. Bean建立
  2. Bean儲存
  3. Bean獲取
  4. 自動完成依賴檢測與注入
  5. 獲取Bean的BeanDefinition
  6. 自動生成Bean

IOC簡單使用

這裡將會通過比較原始的2個Demo,去使用IOC,而不是使用ClassPathXmlApplicationContext

不使用XML配置建立Bean

public class BeanDefinitionReaderDemo {
    @Test
    public void testIOC(){
        // 建立BeanFactory
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();            
        // 建立BeanDefinition
        RootBeanDefinition beanDefinition = new RootBeanDefinition(IOCTestBean.class);  
        // 設定物件域為單例     
        beanDefinition.setScope(BeanDefinition.SCOPE_SINGLETON);       
        // 把BeanDefinition註冊到BeanFactory
        beanFactory.registerBeanDefinition("iocTest", beanDefinition);             
        // 從BeanFactory獲取Bean
        IOCTestBean iocTestBean = beanFactory.getBean("iocTest",IOCTestBean.class);
        iocTestBean.createBean();       
    }   
}
class IOCTestBean{
    public void createBean(){
        System.out.println("IOCTestBean.createBean()");
    }      
}

控制檯輸出: 在這裡插入圖片描述

使用XML配置建立Bean

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
          http://www.springframework.org/schema/beans/spring-beans.xsd">
          	
	<bean id="iocTest" class="com.dk.spring.IOCTestBean" />
</beans>
public class BeanDefinitionReaderDemo {
    @Test
    public void testIOC(){        
        // 建立BeanFactory
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();                      
        // 建立BeanDefinitionReader
        XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
        // 讀取xml配置檔案
        beanDefinitionReader.loadBeanDefinitions("classpath:applicationContext.xml");        
        // 從BeanFactory獲取Bean
        IOCTestBean iocTestBean = beanFactory.getBean("iocTest",IOCTestBean.class);
        iocTestBean.createBean();        
    }   
}
class IOCTestBean{
    public void createBean(){
        System.out.println("IOCTestBean.createBean()");
    }      
}

控制檯輸出: 在這裡插入圖片描述

使用XML配置建立Bean的原始碼解析

1、BeanDefinitionReader載入XML配置檔案,並且把BeanDefinition註冊到BeanFactory

 		// 建立BeanFactory
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();                      
        // 建立BeanDefinitionReader
        XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
        // 讀取xml配置檔案
        beanDefinitionReader.loadBeanDefinitions("classpath:applicationContext.xml");        

1、XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

 	public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) {
		super(registry);
	}
	protected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) {
		Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
		this.registry = registry;

		// Determine ResourceLoader to use.
		if (this.registry instanceof ResourceLoader) {
			this.resourceLoader = (ResourceLoader) this.registry;
		}
		else {
			this.resourceLoader = new PathMatchingResourcePatternResolver();
		}

		// Inherit Environment if possible
		if (this.registry instanceof EnvironmentCapable) {
			this.environment = ((EnvironmentCapable) this.registry).getEnvironment();
		}
		else {
			this.environment = new StandardEnvironment();
		}
	}

建立了兩個物件,分別為

  • PathMatchingResourcePatternResolver,路徑解析器,主要用於把xml配置的路徑解析成Resource物件。
  • StandardEnvironment,配置環境物件,主要用於獲取JDK環境配置、系統環境配置;

2、XmlBeanDefinitionReader.loadBeanDefinitions(“classpath:applicationContext.xml”);

AbstractBeanDefinitionReader.java
	@Override
	public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
		return loadBeanDefinitions(location, null);
	}
AbstractBeanDefinitionReader.java
		public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
		ResourceLoader resourceLoader = getResourceLoader();
		if (resourceLoader == null) {
			throw new BeanDefinitionStoreException(
					"Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
		}

		if (resourceLoader instanceof ResourcePatternResolver) {
			// Resource pattern matching available.
			try {
				Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
				int loadCount = loadBeanDefinitions(resources);
				if (actualResources != null) {
					for (Resource resource : resources) {
						actualResources.add(resource);
					}
				}
				if (logger.isDebugEnabled()) {
					logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");
				}
				return loadCount;
			}
			catch (IOException ex) {
				throw new BeanDefinitionStoreException(
						"Could not resolve bean definition resource pattern [" + location + "]", ex);
			}
		}
		else {
			// Can only load single resources by absolute URL.
			Resource resource = resourceLoader.getResource(location);
			int loadCount = loadBeanDefinitions(resource);
			if (actualResources != null) {
				actualResources.add(resource);
			}
			if (logger.isDebugEnabled()) {
				logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
			}
			return loadCount;
		}
	}

在這裡插入圖片描述

把路徑location通過路徑解析器解析成Resouce物件,然後繼續呼叫loadBeanDefinitions(resources)

AbstractBeanDefinitionReader.java
	@Override
	public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
		Assert.notNull(resources, "Resource array must not be null");
		int counter = 0;
		for (Resource resource : resources) {
			counter += loadBeanDefinitions(resource);
		}
		return counter;
	}
XmlBeanDefinitionReader.java
	public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
		return loadBeanDefinitions(new EncodedResource(resource));
	}

把Resource轉換成帶有編碼的EncodedResource物件

XmlBeanDefinitionReader.java

	private final ThreadLocal<Set<EncodedResource>> resourcesCurrentlyBeingLoaded =
			new NamedThreadLocal<Set<EncodedResource>>("XML bean definition resources currently being loaded");


	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<EncodedResource>(4);
			this.resourcesCurrentlyBeingLoaded.set(currentResources);
		}
		if (!currentResources.add(encodedResource)) {
			throw new BeanDefinitionStoreException(
					"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
		}
		try {
			InputStream inputStream = encodedResource.getResource().getInputStream();
			try {
				InputSource inputSource = new InputSource(inputStream);
				if (encodedResource.getEncoding() != null) {
					inputSource.setEncoding(encodedResource.getEncoding());
				}
				return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
			}
			finally {
				inputStream.close();
			}
		}
		catch (IOException ex) {
			throw new BeanDefinitionStoreException(
					"IOException parsing XML document from " + encodedResource.getResource(), ex);
		}
		finally {
			currentResources.remove(encodedResource);
			if (currentResources.isEmpty()) {
				this.resourcesCurrentlyBeingLoaded.remove();
			}
		}
	}

我們可以把上述拆分為幾個程式碼塊,然後依次分析

  • 獲取當前執行緒(ThreadLocal)正在載入resource的Set集合,如果當前resource在Set集合已經存在,那麼丟擲異常,說明當前resource正在被該執行緒載入;如果不存在,那麼加入Set集合,標記當前執行緒開始載入resource;
 		Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
		if (currentResources == null) {
			currentResources = new HashSet<EncodedResource>(4);
			this.resourcesCurrentlyBeingLoaded.set(currentResources);
		}
		if (!currentResources.add(encodedResource)) {
			throw new BeanDefinitionStoreException(
					"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
		}
  • 獲取該配置檔案的輸入流Inputstream物件,並且把它包裝成InputSource準備進行Java Dom XML解析,然後呼叫doLoadBeanDefinitions(inputSource, encodedResource.getResource());
try {
			InputStream inputStream = encodedResource.getResource().getInputStream();
			try {
				InputSource inputSource = new InputSource(inputStream);
				if (encodedResource.getEncoding() != null) {
					inputSource.setEncoding(encodedResource.getEncoding());
				}
				return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
			}
			finally {
				inputStream.close();
			}
		}
		catch (IOException ex) {
			throw new BeanDefinitionStoreException(
					"IOException parsing XML document from " + encodedResource.getResource(), ex);
		}
  • 最後全部完成,把當前resource成Set集合移除
			currentResources.remove(encodedResource);
			if (currentResources.isEmpty()) {
				this.resourcesCurrentlyBeingLoaded.remove();
			}

我們繼續看doLoadBeanDefinitions(inputSource, encodedResource.getResource())

XmlBeanDefinitionReader.java
	protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
			throws BeanDefinitionStoreException {
		try {
			Document doc = doLoadDocument(inputSource, resource);
			return registerBeanDefinitions(doc, resource);
		}
		catch (BeanDefinitionStoreException ex) {
			throw ex;
		}
		catch (SAXParseException ex) {
			throw new XmlBeanDefinitionStoreException(resource.getDescription(),
					"Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
		}
		catch (SAXException ex) {
			throw new XmlBeanDefinitionStoreException(resource.getDescription(),
					"XML document from " + resource + " is invalid", ex);
		}
		catch (ParserConfigurationException ex) {
			throw new BeanDefinitionStoreException(resource.getDescription(),
					"Parser configuration exception parsing XML from " + resource, ex);
		}
		catch (IOException ex) {
			throw new BeanDefinitionStoreException(resource.getDescription(),
					"IOException parsing XML document from " + resource, ex);
		}
		catch (Throwable ex) {
			throw new BeanDefinitionStoreException(resource.getDescription(),
					"Unexpected exception parsing XML document from " + resource, ex);
		}
	}
  • 呼叫doLoadDocument(inputSource, resource)把inputSource通過JDK自帶的Dom技術,解析成Document文件樹 這裡為什麼使用Java自帶的Dom解析而不用Sax、Dom4J等一些開源的強大解析技術?我猜大概是使用Java自帶的,不用去解決一些相容問題,並且Spring配置檔案小的原因。
  • registerBeanDefinitions(doc, resource);把Document的每個Element解析成BeanDefinition,並且把BeanDefinition注入到BeanFactory
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
		BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
		int countBefore = getRegistry().getBeanDefinitionCount();
		documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
		return getRegistry().getBeanDefinitionCount() - countBefore;
	}
  • createBeanDefinitionDocumentReader(),建立一個用於解析Document的解析器
  • createReaderContext(resource),把Resource包裝成XmlReaderContext物件,該物件具有XML NameSpace解析功能,並且具有XmlBeanDefinitionReader的引用。
  • documentReader.registerBeanDefinitions(doc, createReaderContext(resource));解析Docuemnt
	public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
		this.readerContext = readerContext;
		logger.debug("Loading bean definitions");
		Element root = doc.getDocumentElement();
		doRegisterBeanDefinitions(root);
	}
  • Element root = doc.getDocumentElement();獲取Document的根元素
  • doRegisterBeanDefinitions(root);從根元素開始解析XML配置檔案,並且註冊BeanDefinition
	protected void doRegisterBeanDefinitions(Element root) {
		BeanDefinitionParserDelegate parent = this.delegate;
		this.delegate = createDelegate(getReaderContext(), root, parent);

		if (this.delegate.isDefaultNamespace(root)) {
			String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
			if (StringUtils.hasText(profileSpec)) {
				String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
						profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
				if (!getReaderContext().getEnvironment().acceptsProfiles<