Spring源碼解讀(1)-IOC容器BeanDefinition的加載

1、IOC容器BeanDefinition的加載

一、概述

    spring的兩大核心:IOC(依賴注入)和AOP(面向切面),IOC本質上就是一個線程安全的hashMap,put和get方法就對應IOC容器的bean的註冊和獲取,spring經過讀取xml或者使用註解配置的類生成一個BeanDefinition放入到容器中,獲取的時候經過BeanDefinition的配置經過asm、反射等技術完成屬性的注入最終獲取一個bean,獲取bean的方法就getBean(),咱們無需關心實現細節,直接按照spring提供的註解或者xml配置方式使用便可。java

二、IOC容器

    雖然IOC本質上是一個線程安全的hashMap,使用時直接經過getBean()獲取(@Autowired本質也是經過getBean()獲取),這樣在使用bean實例的時候,就不用關心bean的建立,只管用就好了,IOC會在程序啓動時,自動將依賴的對象注入到目標對象中,很是簡單,省心。可是若是不瞭解IOC中bean的註冊和獲取原理,當使用Spring沒法獲取一個bean的時候,針對拋出的異常可能一頭霧水。node

IOC容器的實現包含了兩個很是重要的過程:web

  • xml的讀取生成BeanDefinition註冊到IOC中
  • 經過BeanDefinition實例化bean,並從IOC中經過getBean()獲取
//spring源碼中將一個bean的BeanDefinition放入到IOC中
this.beanDefinitionMap.put(beanName, beanDefinition);
複製代碼
//Spring源碼中經過beanName獲取一個bean
public Object getBean(String name) throws BeansException {
		assertBeanFactoryActive();
		return getBeanFactory().getBean(name);
	}
複製代碼

這兩個過程還對應了兩個很是核心的接口:spring

    BeanDefinitionRegistry和BeanFactory,一個向IOC中註冊BeanDefinition,一個從IOC獲取Bean實例對象。設計模式

    讀IOC源碼必須從瞭解BeanDefinition開始,BeanDefinition是一個接口,不管是經過xml聲明仍是經過註解定義一個bean實例,在IOC容器中第一步老是爲其對應生成一個BeanDefinition,裏面包含了類的全部基本信息。安全

    其中AbstractBeanDefinition實現BeanDefinition,這個接口有兩個子接口GenericBeanDefinitionRootBeanDefinition,再來看看BeanDefinition中的一些方法bash

-getBeanClassName()獲取bean的全限定名,mvc

  • getScope()獲取該類的做用域,app

  • isLazyInit()該類是否爲懶加載,ide

  • getPropertyValues()獲取配置的屬性值列表(用於setter注入),

  • getConstructorArgumentValues()獲取配置的構造函數值(用於構造器注入)等bean的重要信息,若是經過註解的方式,還會包含一些註解的屬性信息,

    總而言之 ,BeanDefinition包含了咱們定義的一個類的全部信息,而後經過 BeanDefinitionRegistry接口的registerBeanDefinition註冊到IOC容器中,最後經過BeanDefinition結合asm,反射等相關技術,經過BeanFactory接口的getBean()獲取一個實例對象好像也不是什麼困難的事情了。固然這只是表層的大體原理,實際上spring在實現IOC的時候,用了大量的設計模式,好比:單例模式、模板方法、工廠模式、代理模式(AOP基本上全是)等,此外面向對象的基本原則中的單一職責、開放封閉原則等隨處可見,具體的源碼解讀仍是在以後的筆記裏介紹。

三、讀取xml配置文件

    讀取源碼須要經過調試去看,Spring啓動時首先會讀取xml配置文件,xml文件能夠從當前類路徑下讀,也能夠從文件系統下讀取,如下是用於調試的簡單案例:

@Test
    public void testSpringLoad() {
        ClassPathXmlApplicationContext application = new ClassPathXmlApplicationContext("spring/spring-context.xml");
        BankPayService bankPayService = (BankPayService) application.getBean("bankPayService");
        Assert.assertNotNull(bankPayService);
    }
複製代碼

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"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans.xsd
      http://www.springframework.org/schema/context
      http://www.springframework.org/schema/context/spring-context.xsd"
       default-lazy-init="true">
    <bean id="bankPayService" class="com.yms.manager.serviceImpl.BankPayServiceImpl"/>
    <context:property-placeholder location="classpath*:app-env.properties"/>
    <context:component-scan base-package="com.yms.market"/>
</beans>
複製代碼

    執行測試案例,確定是成功的,打斷點開始調試,首先會進入ClassPathXmlApplicationContext的構造函數中:

public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent){

		super(parent);
         //獲取當前環境 裝飾傳入的路徑
		setConfigLocations(configLocations);
		if (refresh) {
            //程序入口
			refresh();
		}
	}
複製代碼

    構造函數中最關鍵的部分是refresh()方法,該方法用於刷新IOC容器數據,該方法由AbstractApplicationContext實現。

@Override
	public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			// Prepare this context for refreshing.
			prepareRefresh();
			// 讓子類去刷新beanFactory 進入這裏查看
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
			// Prepare the bean factory for use in this context.
			prepareBeanFactory(beanFactory);
			try {
				// Allows post-processing of the bean factory in context subclasses.
				postProcessBeanFactory(beanFactory);
				// Invoke factory processors registered as beans in the context.
				invokeBeanFactoryPostProcessors(beanFactory);
				// Register bean processors that intercept bean creation.
				registerBeanPostProcessors(beanFactory);
				// Initialize message source for this context.
				initMessageSource();
				// Initialize event multicaster for this context.
				initApplicationEventMulticaster();
				// Initialize other special beans in specific context subclasses.
				onRefresh();
				// Check for listener beans and register them.
				registerListeners();
				// Instantiate all remaining (non-lazy-init) singletons.
				finishBeanFactoryInitialization(beanFactory);
				// Last step: publish corresponding event.
				finishRefresh();
			}
			catch (BeansException ex) {
				// Destroy already created singletons to avoid dangling resources.
				destroyBeans();
				// Reset 'active' flag.
				cancelRefresh(ex);
				// Propagate exception to caller.
				throw ex;
			}
		}
	}
複製代碼

    在refresh方法中主要完成了加載xml文件的環境配置、xml文件讀取,註冊BeanFactoryPostProcessor處理器、註冊監聽器等工做,其中比較核心的是第二行代碼ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
	      //加載xml配置文件,生成BeanDefinition並註冊到IOC容器中 
          refreshBeanFactory();
         //獲取加載完xml文件以後的beanFactory對象
		ConfigurableListableBeanFactory beanFactory = getBeanFactory();
		if (logger.isDebugEnabled()) {
			logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
		}
		return beanFactory;
	}
複製代碼

    refreshBeanFactory和getBeanFactory都是由AbstractApplicationContext的子類AbstractRefreshableApplicationContext實現的,

@Override
	protected final void refreshBeanFactory() throws BeansException {
        //若是beanFactory不爲空 ,清除老的beanFactory
		if (hasBeanFactory()) {
			destroyBeans();
			closeBeanFactory();
		}
		try {
             //建立一個beanFactory
			DefaultListableBeanFactory beanFactory = createBeanFactory();
			beanFactory.setSerializationId(getId());
             //設置bean是否容許覆蓋 是否容許循環依賴
			customizeBeanFactory(beanFactory);
             //加載beans聲明,即讀取xml或者掃描包 生成BeanDefinition註冊到IOC
			loadBeanDefinitions(beanFactory);
			synchronized (this.beanFactoryMonitor) {
				this.beanFactory = beanFactory;
			}
		}
		catch (IOException ex) {
			throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
		}
	}
複製代碼

    在Spring中DefaultListableBeanFactory是一個很是重要的類,它實現了BeanDefinitionRegistryBeanFactory接口,而且完成了這兩個接口的具體實現,DefaultListableBeanFactory的類圖以下:

    咱們已經知道BeanDefinitionRegistry完成了BeanDefinition的註冊,BeanFactory完成了getBean()中bean的建立,其中xml讀取和bean的 註冊的入口就是loadBeanDefinitions(beanFactory)這個方法,loadBeanDefinitions是一個抽象方法,由類AbstractXmlApplicationContext實現。loadBeanDefinitionsAbstractXmlApplicationContext有不少個重載方法,在不通階段方法使用的參數值不一樣,接下來看看各個loadBeanDefinitions的調用順序:

建立XmlBeanDefinitionReader對象

@Override
	protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
		//Spring將讀取xml操做委託給了XmlBeanDefinitionReader對象
        //而且傳入DefaultListableBeanFactory將生成的beandefinition註冊到IOC中
		XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
		//設置Spring中bean的環境
		beanDefinitionReader.setEnvironment(this.getEnvironment());
		beanDefinitionReader.setResourceLoader(this);
		beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
		// 設置beanDefinitionReader驗證屬性,子類能夠重寫該方法使用自定義reader對象
		initBeanDefinitionReader(beanDefinitionReader);
        //經過beanDefinitionReader讀取xml
		loadBeanDefinitions(beanDefinitionReader);
	}
複製代碼

    Spring對於讀取xml文件。並非由DefaultListableBeanFactory親力親爲,而是委託給了XmlBeanDefinitionReader,在該類內部會將xml配置文件轉換成Resource,Spring封裝了xml文件獲取方式,咱們使用ClassPathXmlApplicationContext讀取xml,所以Spring會經過ClassLoader獲取當前項目工做目錄,並在該目錄下查找spring-context.xml文件,固然咱們還可使用FileSystemXmlApplicationContext從文件系統上以絕對路徑的方式讀取文件

繼續查看第二個loadBeanDefinitions(beanDefinitionReader)

獲取配置文件路徑集合

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
        //默認返回爲空 子類能夠實現該方法 讀取指定文件
		Resource[] configResources = getConfigResources();
		if (configResources != null) {
			reader.loadBeanDefinitions(configResources);
		}
        //獲取咱們配置的xml文件路徑集合
		String[] configLocations = getConfigLocations();
		if (configLocations != null) 
        {
			reader.loadBeanDefinitions(configLocations);
		}
	}
複製代碼

    在這個方法中,最終獲取到了咱們經過ClassPathXmlApplicationContext對象傳進來的xml配置文件路徑,而後由進入委託對象XmlBeanDefinitionReaderloadBeanDefinitions方法中:

@Override
	public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
		Assert.notNull(locations, "Location array must not be null");
		int counter = 0;
		for (String location : locations) {
            //循環讀取配置的全部配置文件路徑
			counter += loadBeanDefinitions(location);
		}
        //返回這次加載的BeanDefinition個數
		return counter;
	}
複製代碼

    在XmlBeanDefinitionReader中,會循環讀取配置的全部配置文件路徑,並將讀取到的bean的聲明建立成BeanDefinition,並將這次生成的數量返回,繼續查看loadBeanDefinitions

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;
		}
	}
複製代碼

    這個方法就是上面所說的Spring將xml配置文件封裝成Resourse,最終獲取到Resourse的過程,這部分代碼沒什麼好看的,就是找到ClassPathResource將xml路徑放進去,而後調用loadBeanDefinitions(resources),再來看這個方法:

@Override
	public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
		return loadBeanDefinitions(new EncodedResource(resource));
	}
複製代碼

    這個方法將Resource封裝城了EncodedResource對象,這個對象有一個屬性encoding,若是設置了xml文件的編碼,在這裏讀取xml文件的時候會根據該編碼進行讀取,繼續往下看:

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());
		}
         //獲取前面加載到的xml配置資源文件
		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 {
              //之因此將路徑都封裝到Resource裏面,就是使其提供一個統一的getInputStream方法
              //獲取文件流對象,XmlBeanDefinitionReader無需關心xml文件怎麼來的
			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();
			}
		}
	}
複製代碼

    spring的代碼風格好像有一個特色,凡是真正開始作事的方法入口都會以do爲前綴,通過前面一系列對xml配置文件的設置,終於來到了doLoadBeanDefinitions(inputSource, encodedResource.getResource()),在這個方法裏Spring會讀取每個Element標籤,並根據命名空間找到對應的NameSpaceHandler去讀取解析Node生成BeanDefinition對象。通過一系列操做Resouse最終會被轉換成InputSource對象,這個類也沒什麼特別的,只是除了文件流以外多了一些參數而已,好比XSD,DTD的publicId,systemId約束,文件流的編碼等,最重要的仍是InputStream,而後來看看這個方法:

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) {
             //主要是校驗文件 經過查找DTD文件約束 校驗文件格式
             //讀取xml文件生成DOC
			Document doc = doLoadDocument(inputSource, resource);
			return registerBeanDefinitions(doc, resource);
	}
複製代碼

這個方法完成兩件事情:

  • 經過inputSource生成Document對象

  • 解析Document並將生成BeanDefinition註冊到IOC中

四、解析DOC生成BeanDefinition

    文件校驗和生成DOC文檔都是一些校驗操做,若是想自定義DTD文檔讓Spring加載,後面還會細說這部份內容,暫且放下,如今主要是看看IOC的BeanDefinition的生成過程,接下來進入registerBeanDefinitions:

public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
		BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
		documentReader.setEnvironment(this.getEnvironment());
		//獲取加載以前IOC容器中的BeanDefinition數量
		int countBefore = getRegistry().getBeanDefinitionCount();
		//具體解析 註冊 
		documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
		//返回本次加載的BeanDefinition數量
		return getRegistry().getBeanDefinitionCount() - countBefore;
	}
複製代碼

    在這個方法裏,首先建立BeanDefinitionDocumentReader,這是個接口用於完成BeanDefinition向IOC容器註冊的功能,Spring只提供了惟一的實現DefaultBeanDefinitionDocumentReader,查看registerBeanDefinitions:

@Override
	public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
		this.readerContext = readerContext;
		logger.debug("Loading bean definitions");
        //root 在這個測試裏就是<beans></beans>
		Element root = doc.getDocumentElement();
		doRegisterBeanDefinitions(root);
	}
複製代碼

    該方法第一步首先回去xml的根節點,在這個測試xml裏就是標籤了,而後將根節點做爲參數傳入到下面的方法中解析doRegisterBeanDefinitions:

protected void doRegisterBeanDefinitions(Element root) {
        //獲取profile環境變量
		String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
		if (StringUtils.hasText(profileSpec)) {
			Assert.state(this.environment != null, "Environment must be set for evaluating profiles");
			String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
					profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
			//判斷該root下的bean是不是前面經過web.xml或者前面設置的bean的環境值
             //若是不是 不須要解析當前root標籤
            if (!this.environment.acceptsProfiles(specifiedProfiles)) {
				return;
			}
		}
		BeanDefinitionParserDelegate parent = this.delegate;
		this.delegate = createDelegate(this.readerContext, root, parent);
        //解析bean所要執行的方法,該方法實際是空的 容許子類擴展 去讀取自定義的node
        //屬於模板方法
		preProcessXml(root);
        //真正解析beans的方法
		parseBeanDefinitions(root, this.delegate);
        //beans解析完以後須要執行的方法,實際也是經過子類擴展 是模板方法
		postProcessXml(root);

		this.delegate = parent;
	}
複製代碼

這個方法看起來不少,其實真正核心的只有兩部分:

  • 讀取beans的profile屬性,判斷是否屬於被激活的組,若是不是則不解析

  • 建立BeanDefinitionParserDelegate,委託該類執行beans解析工做。

最後經過parseBeanDefinitions(root, this.delegate)方法將beans的解析交給BeanDefinitionParserDelegate

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
        //判斷是不是默認的命名空間 也就是beans
		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)) {
                        //判斷是不是xml配置的bean,若是是則調用該方法解析
						parseDefaultElement(ele, delegate);
					}
					else {
                           //不然按照自定義方式解析
						delegate.parseCustomElement(ele);
					}
				}
			}
		}
		else {
             //不然按照自定義方式解析
			delegate.parseCustomElement(root);
		}
	}
複製代碼

    這個方法很重要,這裏已經開始解析<beans>了,首先會判斷,要解析的root是不是beans標籤,若是是再判斷子元素是不是<bean>元素,正常來說,咱們使用spring的時候都會再<beans>標籤下配置,因此不出意外都會走到for循環裏,而後在for循環裏判斷是不是默認命名空間的時候就會發生變化:

  • 若是是則走parseDefaultElement(ele, delegate);

  • 若是是<mvc:annotation-driven>、 <context:component-scan base-package="***"/>等則會走到自定義元素解析delegate.parseCustomElement(ele)裏

    自定義解析加載到最後仍是會跟加載默認命名空間的bean同樣,因此在這裏只分析自定義命名空間的解析,不過值得提一下的是自定義解析方法裏會首先根據Element的命名空間找到NamespaceHandler,而後由該NamespaceHanler去解析

public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
		//獲取元素的命名空間
        String namespaceUri = getNamespaceURI(ele);
        //獲取命名空間解析器
		NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
		if (handler == null) {
			error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
			return null;
		}
        //解析自定義元素
		return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
	}
複製代碼

    因爲後面會本身實現一個NamespaceHandler解析自定義的標籤,會專門說明Spring如何查找NamespaceHandler以及如何解析自定義元素,這裏只是瞭解下NamespaceHandler的概念便可,接着看Spring解析<Beans>

查看parseDefaultElement:

private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
		if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
            //解析 import
			importBeanDefinitionResource(ele);
		}
		else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
            //解析alias
			processAliasRegistration(ele);
		}
		else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
            //解析bean
			processBeanDefinition(ele, delegate);
		}
		else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
			// 若是仍是beans 遞歸調用
			doRegisterBeanDefinitions(ele);
		}
	}
複製代碼

    這個方法裏面就是幾個if判斷,用於解析對應的標籤,其中import alias至關因而去讀取另外一個xml文件,最後仍是會調用解析bean,因此在這裏只看解析bean的方法processBeanDefinition(ele, delegate):

protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
         //將bean的屬性都讀取到到BeanDefinitionHolder上
		BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
		if (bdHolder != null) {
            //若是bean裏面有自定義標籤 來決定是否再次解析
			bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
			try {
				// 將生成的BeanDefinitionHolder註冊到IOC中
				BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
			}
			catch (BeanDefinitionStoreException ex) {
				getReaderContext().error("Failed to register bean definition with name '" +
						bdHolder.getBeanName() + "'", ele, ex);
			}
			// 發送註冊事件
			getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
		}
	}
複製代碼

在這個方法裏 ,以前分析的邏輯才逐漸清晰起來,代碼的條例也很清晰

  • BeanDefinitionParserDelegate將bean標籤的屬性讀取到BeanDefinitionHolder對象中

  • 若是beans下還有其餘自定義標籤決定是否有必要再次解析

  • BeanDefinition註冊到IOC中

  • 發送註冊事件

首先來看第一步,讀取node屬性到BeanDefinitionParserDelegate

public AbstractBeanDefinition parseBeanDefinitionElement( Element ele, String beanName, BeanDefinition containingBean) {
		this.parseState.push(new BeanEntry(beanName));
		String className = null;
		if (ele.hasAttribute(CLASS_ATTRIBUTE)) {
             //獲取class全限定名
			className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
		}
		try {
			String parent = null;
			if (ele.hasAttribute(PARENT_ATTRIBUTE)) {
				parent = ele.getAttribute(PARENT_ATTRIBUTE);
			}
             //設置beanClass或者beanClassName
			AbstractBeanDefinition bd = createBeanDefinition(className, parent);
             //讀取node屬性 將配置的屬性 塞入合適的字段中
			parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
			bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
			parseMetaElements(ele, bd);
             //記錄lookup-method配置
			parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
             //記錄replaced-method配置
			parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
             //解析構造函數(構造器注入)
			parseConstructorArgElements(ele, bd);
            //解析屬性(setter注入)
			parsePropertyElements(ele, bd);
			parseQualifierElements(ele, bd);
			bd.setResource(this.readerContext.getResource());
			bd.setSource(extractSource(ele));
			return bd;
		}
		finally {
			this.parseState.pop();
		}
		return null;
	}

複製代碼

    這個方法完成了讀取一個bean包括將node屬性讀入到BeanDefinition,讀取bean的構造函數配置(是構造器注入的前提),讀取bean的屬性配置(是setter注入的前提),其實將node屬性讀取到BeanDefinition很簡單,僅僅是一一對應而已,真正的複雜點在於讀取構造函數參數、讀取屬性值參數。

五、構造器和屬性參數解析

來看下面一段配置:

<bean id="userDao" class="spring.road.beans.models.UserDao"/>
     <!--setter注入-->
    <bean id="beanService" class="spring.road.beans.models.BeanService">
        <property name="mapper" ref="userDao"/>
        <property name="name" value="lijinpeng"/>
        <property name="sex" value="false"/>
    </bean>
    <!--構造器注入-->
    <bean id="person" class="spring.road.beans.models.Person">
        <constructor-arg name="age" value="26"/>
        <constructor-arg name="name" value="dangwendi"/>
        <constructor-arg name="userDao" ref="userDao"/>
        <constructor-arg name="sex" value="true"/>
    </bean>
複製代碼

這段配置使用了兩種注入方式:

property屬性解析

    setter注入就是咱們經過爲屬性賦值,若是屬性值都是string類型的還很好解決,若是pojo類的屬性值不是String,而是好比像Boolean、int、Date等這些數據的時候,必需要進行數據轉換操做才能夠在getBean()的時候將property配置的屬性經過反射注入到對應的字段裏,這好像也不是什麼困難的事情,可是若是是ref引用類型呢,這個問題該如何解決呢?Spring很巧妙的解決了這個問題,用RuntimeBeanReference來表示ref引用的數據,用TypedStringValue表示普通String字符串。既然一個pojo類的全部配置都會讀取到BeanDefinition,因此在xml中配置的屬性必然也會存儲到BeanDefinition中,繼續看源碼會發現BeanDefinition中用MutablePropertyValues類表示屬性集合,該類中propertyValueList就是property集合數據,Spring用PropertyValue存儲了property的name value信息。

//在BeanDefinition類中
MutablePropertyValues getPropertyValues();
//在MutablePropertyValues類中的屬性
private final List<PropertyValue> propertyValueList;
//在PropertyValue中的屬性
private final String name;
private final Object value;
複製代碼

    根據上面xml配置能夠得知value可能須要類型轉換,也多是引用ref,鑑於getBean階段沒法直接賦值,因此須要一箇中間類保存數據,在getBean()反射階段根據類型去轉換成對象,再次查看parsePropertyElements方法:

public void parsePropertyElements(Element beanEle, BeanDefinition bd) {
		NodeList nl = beanEle.getChildNodes();
		for (int i = 0; i < nl.getLength(); i++) {
			Node node = nl.item(i);
             //解析bean下的property屬性節點
			if (isCandidateElement(node) && nodeNameEquals(node, PROPERTY_ELEMENT)) {
				parsePropertyElement((Element) node, bd);
			}
		}
	}
複製代碼

parsePropertyElement解析bean下的property屬性節點

public void parsePropertyElement(Element ele, BeanDefinition bd) {
          //獲取property的name 這個很簡單
		String propertyName = ele.getAttribute(NAME_ATTRIBUTE);
		if (!StringUtils.hasLength(propertyName)) {
			error("Tag 'property' must have a 'name' attribute", ele);
			return;
		}
		this.parseState.push(new PropertyEntry(propertyName));
		try {
			if (bd.getPropertyValues().contains(propertyName)) {
				error("Multiple 'property' definitions for property '" + propertyName + "'", ele);
				return;
			}
            //獲取獲取property的value 這個須要用中間類表示
			Object val = parsePropertyValue(ele, bd, propertyName);
			PropertyValue pv = new PropertyValue(propertyName, val);
			parseMetaElements(ele, pv);
			pv.setSource(extractSource(ele));
			bd.getPropertyValues().addPropertyValue(pv);
		}
		finally {
			this.parseState.pop();
		}
	}
複製代碼

山重水複疑無路,柳暗花明又一村,下面方法便是實現過程:

public Object parsePropertyValue(Element ele, BeanDefinition bd, String propertyName) {
		String elementName = (propertyName != null) ?
						"<property> element for property '" + propertyName + "'" :
						"<constructor-arg> element";

		// Should only have one child element: ref, value, list, etc.
		NodeList nl = ele.getChildNodes();
		Element subElement = null;
		for (int i = 0; i < nl.getLength(); i++) {
			Node node = nl.item(i);
			if (node instanceof Element && !nodeNameEquals(node, DESCRIPTION_ELEMENT) &&
					!nodeNameEquals(node, META_ELEMENT)) {
				// Child element is what we're looking for.
				if (subElement != null) {
					error(elementName + " must not contain more than one sub-element", ele);
				}
				else {
					subElement = (Element) node;
				}
			}
		}
         //是否有ref屬性
		boolean hasRefAttribute = ele.hasAttribute(REF_ATTRIBUTE);
        //是否有value屬性
		boolean hasValueAttribute = ele.hasAttribute(VALUE_ATTRIBUTE);
        //ref和value只能存在一個
		if ((hasRefAttribute && hasValueAttribute) ||
				((hasRefAttribute || hasValueAttribute) && subElement != null)) {
			error(elementName +
					" is only allowed to contain either 'ref' attribute OR 'value' attribute OR sub-element", ele);
		}

		if (hasRefAttribute) {
			String refName = ele.getAttribute(REF_ATTRIBUTE);
			if (!StringUtils.hasText(refName)) {
				error(elementName + " contains empty 'ref' attribute", ele);
			}
             //若是是ref 則轉換成RuntimeBeanReference
			RuntimeBeanReference ref = new RuntimeBeanReference(refName);
			ref.setSource(extractSource(ele));
			return ref;
		}
		else if (hasValueAttribute) {
             //若是是String則轉換成TypedStringValue
			TypedStringValue valueHolder = new TypedStringValue(ele.getAttribute(VALUE_ATTRIBUTE));
			valueHolder.setSource(extractSource(ele));
			return valueHolder;
		}
		else if (subElement != null) {
			return parsePropertySubElement(subElement, bd);
		}
		else {
			// Neither child element nor "ref" or "value" attribute found.
			error(elementName + " must specify a ref or value", ele);
			return null;
		}
	}
複製代碼

    看有註釋行的代碼,結合上面的分析,大概能瞭解setter注入的第一階段BeanDefinition保存屬性數據的方式了,在調用BeanFactorygetBean()方法時,在反射階段獲取到值對象時能夠根據類型去獲取值,若是是TypedStringValue則只需校驗值是否應該轉換,若是須要轉換便可,至於如何轉換,若是是RuntimeBeanReference更簡單了,直接經過getBean()獲取就行了,請記住這兩個類型,在分析getBean()階段屬性值解析的時候就會用到他們。

構造函數constructor-arg解析

     構造器注入其實比setter注入要稍微麻煩一點,之因此說麻煩其實就是要藉助相關技術去實現,由於構造器可能會有不少重載,在xml配置中若是參數順序不一樣可能會調用不一樣的構造函數,致使注入失敗,因此若是要保存構造參數值,必須匹配到惟一合適的構造函數,而且在xml配置的constructor-arg必須按照必定規則與匹配的構造函數一一對應,才能夠在getBean()階段注入成功。

    當我本身嘗試去寫的時候,覺得只須要經過反射獲取構造函數的參數名便可,可是很不幸,經過反射拿到的參數名是諸如arg1 arg2 這樣的name,因此只能經過讀取類的字節碼文件了, 之前看過《深刻了解java虛擬機》這本書,知道能夠經過讀取字節碼文件的方式獲取參數名,可是裏面的各類索引,字段表集合啊什麼的想記住真的好難,並且個人水平還遠遠達不到那個高度,因此就用現成的吧, 我當時是用javassite實現的,看了spring的源碼,發現spring是用asm實現的,固然這個階段是在getBean()階段實現的,之因此介紹是由於必需要先了解爲何Spring要這麼保存構造參數,後面的getBean在分析這塊源碼,仍是先來看看構造函數參數在BeanDefinition的保存吧。

    spring容許在xml中經過index、name、type來指定一個參數,在BeanDefinition中使用ConstructorArgumentValues存儲構造函數參數集合,在ConstructorArgumentValues包含了兩個集合一個配置了索引的indexedArgumentValues參數集合,另外一個沒有配置索引的enericArgumentValues構造函數參數集合,而後構造函數參數值用內部類ValueHolder表示,這個類裏包含了參數的value,類型,參數名等。

//在BeanDefinition類中 
ConstructorArgumentValues getConstructorArgumentValues();
//在ConstructorArgumentValues類中
//使用了索引的構造函數參數值集合
private final Map<Integer, ValueHolder> indexedArgumentValues = new LinkedHashMap<Integer,          ValueHolder>(0);  
//未使用索引的構造函數參數值集合
private final List<ValueHolder> genericArgumentValues = new LinkedList<ValueHolder>();
//在ValueHolder中的屬性
private Object value;
private String type;
private String name;
private Object source;
複製代碼

構造函數參數的存儲結構分析完了,接下來看看代碼吧,其實存儲和屬性值的存儲是同樣的 ,這裏只看關鍵的代碼:

public void parseConstructorArgElements(Element beanEle, BeanDefinition bd) {
		NodeList nl = beanEle.getChildNodes();
		for (int i = 0; i < nl.getLength(); i++) {
			Node node = nl.item(i);
			if (isCandidateElement(node) && nodeNameEquals(node, CONSTRUCTOR_ARG_ELEMENT)) {
				parseConstructorArgElement((Element) node, bd);
			}
		}
	}
複製代碼

解析constructor-arg標籤parseConstructorArgElement

public void parseConstructorArgElement(Element ele, BeanDefinition bd) {
         //獲取index
		String indexAttr = ele.getAttribute(INDEX_ATTRIBUTE);
         //獲取type
		String typeAttr = ele.getAttribute(TYPE_ATTRIBUTE);
         //獲取name
		String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
        //若是index不爲空 保存到indexedArgumentValues集合中
		if (StringUtils.hasLength(indexAttr)) {
			try {
				int index = Integer.parseInt(indexAttr);
				if (index < 0) {
					error("'index' cannot be lower than 0", ele);
				}
				else {
					try {
						this.parseState.push(new ConstructorArgumentEntry(index));
                          //將value轉換成RuntimeBeanReference或者TypedStringValue
						Object value = parsePropertyValue(ele, bd, null);
						ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(value);
                          //保存type
						if (StringUtils.hasLength(typeAttr)) {
							valueHolder.setType(typeAttr);
						}
                            //保存name
						if (StringUtils.hasLength(nameAttr)) {
							valueHolder.setName(nameAttr);
						}
						valueHolder.setSource(extractSource(ele));
						if (bd.getConstructorArgumentValues().hasIndexedArgumentValue(index)) {
							error("Ambiguous constructor-arg entries for index " + index, ele);
						}
						else {
                               //保存構造函數參數值
							bd.getConstructorArgumentValues().addIndexedArgumentValue(index, valueHolder);
						}
					}
					finally {
						this.parseState.pop();
					}
				}
			}
			catch (NumberFormatException ex) {
				error("Attribute 'index' of tag 'constructor-arg' must be an integer", ele);
			}
		}
         //index未配置 保存到普通集合中genericArgumentValues
		else {
			try {
				this.parseState.push(new ConstructorArgumentEntry());
				Object value = parsePropertyValue(ele, bd, null);
				ConstructorArgumentValues.ValueHolder valueHolder = new ConstructorArgumentValues.ValueHolder(value);
				if (StringUtils.hasLength(typeAttr)) {
					valueHolder.setType(typeAttr);
				}
				if (StringUtils.hasLength(nameAttr)) {
					valueHolder.setName(nameAttr);
				}
				valueHolder.setSource(extractSource(ele));
                  //保存構造函數參數值
				bd.getConstructorArgumentValues().addGenericArgumentValue(valueHolder);
			}
			finally {
				this.parseState.pop();
			}
		}
	}

複製代碼

    至此,一個xml配置的bean被徹底存放到了BeanDefinition中,其實基於掃描註解配置也是同樣的,只不過在作不少清理工做,針對下面配置簡要說明下基於註解的處理:

<context:component-scan base-package="com.yms.market"/>
複製代碼
  • 首先spring讀取到這個node,會查找該node的NameSpaceHandler,而後調用parse方法解析

  • 而後讀取到屬性base-package,轉換成對應路徑後查找該路徑下全部的class文件

  • 讀取class文件的註解,查看是否實現了特定註解,若是實現了註解則處裏方式與xml配置的處理相同,不然不處理。

    真實的處理過程比較複雜,也是用了不少設計模式,用了不少類來處理,可是我想說的是,不管是用過註解仍是經過xml,最終的處理方式都是同樣的,都是先生成一個BeanDefinition註冊到IOC中,而後經過getBean()獲取。

六、BeanDefinitionRegistry註冊

BeanDefinition建立完成了,還差最後一步,將生成的BeanDefinition註冊到IOC中,這就必須往回看了。

protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
         //將bean的屬性都讀取到到BeanDefinitionHolder上
		BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
		if (bdHolder != null) {
            //若是bean裏面有自定義標籤 來決定是否再次解析
			bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
			try {
				// 將生成的BeanDefinitionHolder註冊到IOC中
				BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
			}
			catch (BeanDefinitionStoreException ex) {
				getReaderContext().error("Failed to register bean definition with name '" +
						bdHolder.getBeanName() + "'", ele, ex);
			}
			// 發送註冊事件
			getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
		}
	}
複製代碼

這段代碼應該還很熟悉,此次看最後一步registerBeanDefinition

public static void registerBeanDefinition( BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) throws BeanDefinitionStoreException {

		// 獲取beanName
		String beanName = definitionHolder.getBeanName();
        //註冊BeanDefinition,key爲beanName,value是BeanDefinition
		registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
		// 若是配置別名的話獲取別名
		String[] aliases = definitionHolder.getAliases();
		if (aliases != null) {
			for (String aliase : aliases) {
                //註冊別名
				registry.registerAlias(beanName, aliase);
			}
		}
	}
複製代碼

該方法完成了如下事情

  • BeanDefinition用beanName做爲key註冊到IOC容器中
  • 若是配置了別名,將beanName與別名映射起來。

     再來看具體的註冊過程registry.registerBeanDefinition,註冊是調用BeanDefinitionRegistryregisterBeanDefinition方法,在剛開始的分析說過DefaultListableBeanFactory實現了BeanDefinitionRegistryBeanFactory,並且實現了具體邏輯,下面的內容就是Spring註冊的過程,爲了看的清晰我省去了不少異常和無用的代碼:

@Override
	public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) {
		if (beanDefinition instanceof AbstractBeanDefinition) {
			try {
                //驗證
				((AbstractBeanDefinition) beanDefinition).validate();
			}
			catch (BeanDefinitionValidationException ex) {
			}
		}
        //這裏保證了線程安全
		synchronized (this.beanDefinitionMap) {
			BeanDefinition oldBeanDefinition = this.beanDefinitionMap.get(beanName);
			if (oldBeanDefinition != null) {
				if (!this.allowBeanDefinitionOverriding) {
			       //不容許覆蓋拋出異常
				}
				else if (oldBeanDefinition.getRole() < beanDefinition.getRole()) {
					if (this.logger.isWarnEnabled()) {
	
				}
				else {
					if (this.logger.isInfoEnabled()) {
					
					}
				}
			}
			else {
				this.beanDefinitionNames.add(beanName);
				this.frozenBeanDefinitionNames = null;
			}
             //註冊進去嘍
			this.beanDefinitionMap.put(beanName, beanDefinition);
		}
		resetBeanDefinition(beanName);
	}
複製代碼

    到此爲止,IOC容器的第一步爲bean生成BeanDefinition並註冊到IOC容器中完成,接下來就是第二步,經過BeanFactory實現依賴注入了。

相關文章
相關標籤/搜索