1. 程式人生 > 實用技巧 >Spring原始碼02---obtainFreshBeanFactory

Spring原始碼02---obtainFreshBeanFactory

容器重新整理前配置:https://www.cnblogs.com/xiaomaomao/p/14046219.html

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
	if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
		// The application context id is still set to its original default value
		// -> assign a more useful id based on available information
		String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
		if (idParam != null) {
			wac.setId(idParam);
		}
		else {
			// Generate default id...
			wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
					ObjectUtils.getDisplayString(sc.getContextPath()));
		}
	}

	wac.setServletContext(sc);
	String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
	if (configLocationParam != null) {
		wac.setConfigLocation(configLocationParam);
	}

	// The wac environment's #initPropertySources will be called in any case when the context
	// is refreshed; do it eagerly here to ensure servlet property sources are in place for
	// use in any post-processing or initialization that occurs below prior to #refresh
	ConfigurableEnvironment env = wac.getEnvironment();
	if (env instanceof ConfigurableWebEnvironment) {
		((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
	}

	customizeContext(sc, wac);
	// 呼叫 AbstractApplicationContext 的 refresh 方法
	wac.refresh();
}

wac 是 XmlWebApplicationContext 型別的,XmlWebApplicationContext 的繼承結構如下:

這裡的 wac 是 XmlWebApplicationContext 型別的,在它本類中沒有找到 refresh() 方法,那麼就往上找,結果在這張圖的最上層介面ConfigurableApplicationContext中找到了 refresh() 方法,而ConfigurableApplicationContext 介面的實現類中只有AbstractApplicationContext 這個抽象類實現了 refresh() 方法,所以我們最終會去到 AbstractApplicationContext 類中的 refresh()

找到AbstractApplicationContext 類中的 refresh() 方法,我們主要看一下這個方法到底做了什麼?

public void refresh() throws BeansException, IllegalStateException {
	// 同步鎖,如果 refresh() 方法還沒有執行完成,這個時候你突然繼續再來一次容器啟動或者容器銷燬的動作,那麼就亂套了
	synchronized (this.startupShutdownMonitor) {

		prepareRefresh();

		// 告知子類重新整理內部的 Bean Factory
		// 這步比較關鍵,這步完成後,配置檔案就會解析成一個個 Bean 定義,並註冊到 BeanFactory 中.
        // 當然,這裡說的 Bean 還沒有初始化,只是配置資訊都提取出來了,
        // 註冊也只是將這些資訊都儲存到了註冊中心(說到底核心是一個 beanName-> beanDefinition 的 map)
		ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
	}
	......
}

整個 refresh() 方法是 Spring IOC 的核心方法,可以看到 refresh 裡面有很多的方法,但是我們這裡探究的是重新整理 BeanFactory 的操作,看看重新整理 BeanFactory 的時候到底執行了哪一些的操作ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory()

首先我們來到obtainFreshBeanFactory() 方法

程式碼塊一、obtainFreshBeanFactory()

// AbstractApplicationContext 類中的方法
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
	// 1、重新整理 BeanFactory,由 AbstractRefreshableApplicationContext 實現----(詳細見程式碼塊二)
	refreshBeanFactory();
	// 2、獲取剛剛建立的 BeanFactory
	ConfigurableListableBeanFactory beanFactory = getBeanFactory();
	if (logger.isDebugEnabled()) {
		logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
	}
	// 3、返回 BeanFactory
	return beanFactory;
}

程式碼塊二、refreshBeanFactory()

// AbstractRefreshableApplicationContext 類中的方法
protected final void refreshBeanFactory() throws BeansException {
	// 如果 ApplicationContext 中已經載入過了 BeanFactory ,銷燬所有 Bean ,關閉 BeanFactory
	// 注意:應用中 BeanFactory 本來就是可以多個的,這裡可不是說應用全域性是否有 BeanFactory,而是當前的
	// ApplicationContext 是否持有 BeanFactory
	if (hasBeanFactory()) {
		destroyBeans();
		closeBeanFactory();
	}
	try {
		// 2、建立 BeanFactory ,為什麼那麼多的 IOC 容器,單單要建立 DefaultListableBeanFactory 呢?這個下面會說到
		DefaultListableBeanFactory beanFactory = createBeanFactory();
		// 3、設定序列化 ID
		beanFactory.setSerializationId(getId());
		// 4、設定 BeanFactory 的兩個屬性,是否允許 bean 覆蓋、是否允許迴圈引用
		customizeBeanFactory(beanFactory);
		// 5、載入 Bean 定義----(詳細見程式碼塊三)
		loadBeanDefinitions(beanFactory);
		synchronized (this.beanFactoryMonitor) {
			this.beanFactory = beanFactory;
		}
	}
	catch (IOException ex) {
		throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
	}
}

createBeanFactory() 這個步驟返回的是一個DefaultListableBeanFactory 型別,我們都知道 Spring 中有眾多的 IOC 容器,為什麼這裡建立的是DefaultListableBeanFactory 呢?我們可以通過下面這張繼承關係圖可以得出結論

通過上面這張圖我們可以看出 ConfigurableListableBeanFactory 實現了第二層級的三個介面,並且它只有一個實現類 DefaultListableBeanFactory ,DefaultListableBeanFactory 這個實現類又通過繼承 AbstractAutowireCapableBeanFactory 抽象類把整張圖的功能都囊括了,這是其它的 BeanFactory 都做不到的,所以既然它的功能是最全的,這就是我們選擇DefaultListableBeanFactory 作為 BeanFactory 的實現類的原因.

程式碼塊三、loadBeanDefinitions(beanFactory)

// XmlWebApplicationContext 中的方法
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
	// 1、為指定的 beanFactory 建立一個 XmlBeanDefinitionReader
	XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

	// 2、設定環境資訊
	beanDefinitionReader.setEnvironment(getEnvironment());
	// 3、將 XmlWebApplicationContext 賦值給 beanDefinitionReader 的 resourceLoader 屬性
	// XmlWebApplicationContext 實現了 ResourceLoader 介面
	beanDefinitionReader.setResourceLoader(this);
	// 4、設定實體解析器
	beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

	// Allow a subclass to provide custom initialization of the reader,
	// then proceed with actually loading the bean definitions.
	// 5、空方法,留給子類去重寫的方法
	initBeanDefinitionReader(beanDefinitionReader);
	// 6、載入 BeanDefinition(核心方法) ----(詳情見程式碼塊四)
	loadBeanDefinitions(beanDefinitionReader);
}

程式碼塊四、loadBeanDefinitions(beanDefinitionReader)

// XmlWebApplicationContext 中的方法
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
	// 1、獲取配置檔案路徑,如果 web.xml 中配置了 contextConfigLocation ,使用它的值作為 Spring 配置檔案路徑
	// 如果沒有配置 contextConfigLocation ,那麼就使用 spring 預設的配置檔案 /WEB/INF/application.xml
	// (詳情見程式碼塊五)
	String[] configLocations = getConfigLocations();
	if (configLocations != null) {
		// 2、遍歷配置檔案路徑,因為配置檔案的路徑可以指定多個
		for (String configLocation : configLocations) {
			// 3、根據其中的一個配置檔案路徑載入 BeanDefinitions ----(詳情見程式碼塊六)
			reader.loadBeanDefinitions(configLocation);
		}
	}
}

程式碼塊五、getConfigLocations()

// AbstractRefreshableWebApplicationContext 類中的方法
public String[] getConfigLocations() {
	// 1、呼叫父類方法
	return super.getConfigLocations();
}

// AbstractRefreshableConfigApplicationContext 類中的方法
protected String[] getConfigLocations() {
	// 1、如果 web.xml 中配置了 contextConfigLocation ,使用該引數對應的值作為 spring 的配置檔案
	// 2、如果沒有配置,則使用 getDefaultConfigLocations()
	return (this.configLocations != null ? this.configLocations : getDefaultConfigLocations());
}

// XmlWebApplicationContext 類中的方法
protected String[] getDefaultConfigLocations() {
	if (getNamespace() != null) {
		return new String[] {DEFAULT_CONFIG_LOCATION_PREFIX + getNamespace() + DEFAULT_CONFIG_LOCATION_SUFFIX};
	}
	else {
		// spring 預設的配置檔案路徑 /WEB/INF/application.xml
		return new String[] {DEFAULT_CONFIG_LOCATION};
	}
}

程式碼塊六、loadBeanDefinitions(configLocation)

// AbstractBeanDefinitionReader 類中的方法
public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
	// 1、呼叫本類中兩個引數的構造方法,第二個引數值為 null ---- (詳情見程式碼塊七)
	return loadBeanDefinitions(location, null);
}

程式碼塊七、loadBeanDefinitions(location, null)

// AbstractBeanDefinitionReader 類中的方法
public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
	// 1、獲取 ResourceLoader ,我們這裡是 XmlWebApplicationContext
	ResourceLoader resourceLoader = getResourceLoader();
	// 2、如果類載入器為空,丟擲異常
	if (resourceLoader == null) {
		throw new BeanDefinitionStoreException(
				"Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
	}
	// 3、判斷 resourceLoader 是否為 ResourcePatternResolver 的例項
	if (resourceLoader instanceof ResourcePatternResolver) {
		try {
			// 3.1、將 String 型別的配置檔名根據路徑、前後綴等進行匹配獲取到符合條件的配置檔案,然後轉成 Resource 型別的陣列
			Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
      // 3.2、根據 resources 載入 BeanDefinitions ---- (詳情見程式碼塊八)
			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 + "]");
			}
			// 3.3、返回載入 BeanDefinition 的個數
			return loadCount;
		}
		catch (IOException ex) {
			throw new BeanDefinitionStoreException(
					"Could not resolve bean definition resource pattern [" + location + "]", ex);
		}
	}
	else {
		// 4、通過絕對路徑來載入資源,因為是絕對路徑,只能載入一個配置檔案
		Resource resource = resourceLoader.getResource(location);
		// 5、通過絕對路徑來載入 BeanDefinitions
		int loadCount = loadBeanDefinitions(resource);
		if (actualResources != null) {
			actualResources.add(resource);
		}
		if (logger.isDebugEnabled()) {
			logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
		}
		return loadCount;
	}
} 

程式碼塊八、loadBeanDefinitions(resources)

// AbstractBeanDefinitionReader 類中的方法
public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
	Assert.notNull(resources, "Resource array must not be null");
	int counter = 0;
	for (Resource resource : resources) {
		// 1、根據單個 Spring 配置檔案載入 BeanDefinitions ---- (詳情見程式碼塊九)
		counter += loadBeanDefinitions(resource);
	}
	// 2、返回最後總共載入的 BeanDefinition
	return counter;
}

程式碼塊九、loadBeanDefinitions(resource)

// XmlBeanDefinitionReader 類中的方法
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
	// 1、載入 BeanDefinition ---- (詳情見程式碼塊十)
	return loadBeanDefinitions(new EncodedResource(resource));
}

程式碼塊十、loadBeanDefinitions(new EncodedResource(resource))

// XmlBeanDefinitionReader 類中的方法
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());
			}
			// 1、核心方方法,執行載入 BeanDefiniton ----(詳情見程式碼塊十一)
			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();
		}
	}
}

程式碼塊十一、doLoadBeanDefinitions(inputSource, encodedResource.getResource())

// XmlBeanDefinitionReader 類中的方法
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
			throws BeanDefinitionStoreException {
	try {
		// 1、根據當前的一個 Spring 的配置檔案建立 Document 物件
		Document doc = doLoadDocument(inputSource, resource);
		// 2、註冊 BeanDefinition ---- (詳情見程式碼塊十二)
		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);
	}
}

程式碼塊十二、registerBeanDefinitions(doc, resource)

// XmlBeanDefinitionReader 類中的方法
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
	// 1、建立 BeanDefinitionDocumentReader 物件
	BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
	// 2、獲取之前註冊的 BeanDefinition 的個數
	int countBefore = getRegistry().getBeanDefinitionCount();
	// 3、註冊 BeanDefinition ---- (詳情見程式碼塊十三)
	documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
	// 4、返回本次註冊的 BeanDefinition 總數目
	return getRegistry().getBeanDefinitionCount() - countBefore;
}

程式碼塊十三、registerBeanDefinitions(doc, createReaderContext(resource))

// DefaultBeanDefinitionDocumentReader 類中的方法
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
	this.readerContext = readerContext;
	logger.debug("Loading bean definitions");
	// 1、獲取 Document 元素的根節點, Spring 配置檔案的根節點一般都是 beans
	Element root = doc.getDocumentElement();
	// 2、根據根節點註冊 BeanDefinitions ---- (詳情見程式碼塊十四)
	doRegisterBeanDefinitions(root);
}

程式碼塊十四、doRegisterBeanDefinitions(root)

protected void doRegisterBeanDefinitions(Element root) {
	// 1、
	BeanDefinitionParserDelegate parent = this.delegate;
	this.delegate = createDelegate(getReaderContext(), root, parent);
	// 1、校驗 root 節點的名稱空間是否為預設的名稱空間(Spring 預設名稱空間http://www.springframework.org/schema/beans)
	if (this.delegate.isDefaultNamespace(root)) {
		// 2、獲取 Profile 屬性
		String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
		if (StringUtils.hasText(profileSpec)) {
			String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
					profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
			// 3、校驗當前節點的 profile 是否符合當前環境定義的,,如果不是則直接跳過,不解析該節點下的內容
			if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
				if (logger.isInfoEnabled()) {
					logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec +
							"] not matching: " + getReaderContext().getResource());
				}
				return;
			}
		}
	}
	// 4、鉤子方法,留給子類實現
	preProcessXml(root);
	// 5、解析 BeanDefinitions ---- (詳情見程式碼塊十五)
	parseBeanDefinitions(root, this.delegate);
	// 6、鉤子方法,留給子類實現
	postProcessXml(root);
	this.delegate = parent;
}

profile屬性主要用於多環境開發,用於切換不同的環境,例如下圖:

我們可以在配置檔案中同時寫上多套配置來適用於 dev 環境、sit 環境、uat 環境,這樣可以方便的進行切換開發、部署環境,最常用的就是更換不同的資料庫.具體使用哪個環境在 web.xml 中通過引數spring.profiles.active來配置,如下圖

程式碼塊十五、parseBeanDefinitions(root, this.delegate)

// DefaultBeanDefinitionDocumentReader 類中的方法
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)) {
					// 1、解析預設名稱空間下面的預設元素
					parseDefaultElement(ele, delegate);
				}
				else {
					// 2、解析預設名稱空間下面的非預設元素
					delegate.parseCustomElement(ele);
				}
			}
		}
	}
	else {
		// 3、解析自定義名稱空間下的元素
		delegate.parseCustomElement(root);
	}
}

先看一張圖,這裡面的不帶字首的就是預設的名稱空間,對應的是:xmlns="http://www.springframework.org/schema/beans ,該空間中預設的元素有 import、alias、beans、description、import

預設空間下的非預設元素我們經常用到的主要有<context:component-scan base-package="com.bocom"> 、<tx:annotation-driven/> 等,不過要使用這些標籤需要引入對應的名稱空間例如 context、tx

自定義的名稱空間就是自己定義的名稱空間

參考:https://joonwhee.blog.csdn.net/article/details/86563620