Spring 源碼閱讀(一):Bean 的加載過程

在使用 Spring 的過程中,不知道大家有時候是否像我有一樣的疑問,都說 Spring 主要提供兩大機制:IoC 容器AOP 編程,而 IoC 容器是根本,提供控制反轉的功能,我們在使用的過程中只管聲明 bean 或使用註解的方式,IoC 容器就爲我們管理這些對象,並且幫我注入對象依賴,那麼這一切都是怎麼做到的呢?既然有這樣的疑問,那就得去弄明白,而想明白 IoC 容器的原理,首先就得需明白 Spring 是怎麼加載我們聲明的 bean,所以通過這篇文章來捋捋 Spring 加載 bean 的原理。
一個使用 Spring 較爲簡單的方式就是通過 ClassPathXmlApplicationContext 來啓動 Spring。

public static void main(String[] args) {
    ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring.xml");
}

接下來就探索下這樣簡單的一條語句背後下到底做了什麼事情。

/**
 * Create a new ClassPathXmlApplicationContext, loading the definitions
 * from the given XML file and automatically refreshing the context.
 */
public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
	this(new String[] {configLocation}, true, null);
}
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
			throws BeansException {
	// 調用父類 AbstractApplicationContext 的構造函數,設置父容器以及
	// 初始化路勁解析策略: PathMatchingResourcePatternResolver
	super(parent);
	// 設置 configLocations,因爲有可能我們傳的路徑存在佔位符,需要解析,因此此時
	// 會創建 Environment 對象,具體爲 StandardEvironment
	setConfigLocations(configLocations);
	// refresh 默認爲 true
	if (refresh) {
		// 刷新容器
		refresh();
	}
}

**PS:**可以得知默認父容器是爲 null
上面方法最核心的就是 refresh() 這行代碼了,所以看看你 AbstractApplicationContext 類的 refresh 具體做了哪些事情。

public void refresh() throws BeansException, IllegalStateException {
	synchronized (this.startupShutdownMonitor) {
		// 準備階段:初始化 PropertySource;驗證必須要的屬性是否存在
		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) {
			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();
		}
	}
}

在只要加載 bean 定義的原理時,對 refresh() 方法內部對其他方法的調用可以先不深入瞭解,目前只需要對 AbstractRefreshableApplicationConttext 類的obtainFreshBeanFactory() 深入瞭解下,因爲這個方法內部會有一行代碼去做我們現在想要知道的事情。

/**
 * This implementation performs an actual refresh of this context's underlying
 * bean factory, shutting down the previous bean factory (if any) and
 * initializing a fresh bean factory for the next phase of the context's lifecycle.
 */
protected final void refreshBeanFactory() throws BeansException {
	if (hasBeanFactory()) {
		destroyBeans();
		closeBeanFactory();
	}
	try {
		DefaultListableBeanFactory beanFactory = createBeanFactory();
		beanFactory.setSerializationId(getId());
		// 對 BeanFactory 進行自定義配置,如是否可以重寫 bean 定義,是否允許循環引用
		customizeBeanFactory(beanFactory);
		// 加載 bean definition
		loadBeanDefinitions(beanFactory);
		synchronized (this.beanFactoryMonitor) {
			this.beanFactory = beanFactory;
		}
	}
	catch (IOException ex) {
		throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
	}
}

可以看到,此方法除了創建 BeanFactory 外,還有一行代碼值得我們關注,那就是 loadBeanDefinitions(beanFactory),這行代碼做的事情就是去加載 bean 的定義。不知道大家有沒有想過 Spring 內部是怎麼表達我們在 xml 文件中聲明的 bean 的信息。沒錯,Spring 就是使用了 BeanDefinition 來對其進行表達的,類圖結構如下:
BeanDefinition 類圖
在對 BeanDefinition 類圖有個簡單瞭解下之後,我們看看 AbstractXmlApplicationContext 類的 loadBeanDefinitions() 方法。

protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
	// 爲指定的 BeanFactory 創建 XMLBeanDefinitionReader
	XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

	// 配置 BeanDefinitionReader
	beanDefinitionReader.setEnvironment(this.getEnvironment());
	beanDefinitionReader.setResourceLoader(this);
	beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
 
 	// 鉤子方法,讓子類有機會對 BeanDefinitionReader 進行定製化
	initBeanDefinitionReader(beanDefinitionReader);
	// 加載 BeanDefinition
	loadBeanDefinitions(beanDefinitionReader);
}

可以看到其實此方法並沒有去加載 BeanDefinition,而是對 BeanDefinitionReader 進行設置和定製化,如果此方法名爲 initReader() 或者其他的可能更合適。那我們就在看看接下來的 loadBeanDefinitions() 又做了啥。

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
	// 默認爲 null,子類可以重寫
	Resource[] configResources = getConfigResources();
	if (configResources != null) {
		reader.loadBeanDefinitions(configResources);
	}
	
	// 獲取傳遞給 ClassPathXmlApplicationContext 構造函數的參數,即配置文件
	String[] configLocations = getConfigLocations();
	if (configLocations != null) {
		// 讀取文件
		reader.loadBeanDefinitions(configLocations);
	}
}

此處還是沒有真正看到加載 BeanDefinition 的邏輯,還是在做準備階段,此時做的是獲取配置信息源,然後根據不同的來源調用不同的重載方法,我們就已其中最常見的一種形式說明。

public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
	Assert.notNull(locations, "Location array must not be null");
	int counter = 0;
	for (String location : locations) {
		// 加載 BeanDefinition
		counter += loadBeanDefinitions(location);
	}
	return counter;
}

因爲用戶可能爲了更好的組織信息,對不同的配置信息放在不同配置文件中,因此需要循環的去加載每個文件中的 BeanDefinition。

public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
	// 默認爲使用的容器,此時是 ClassPathXmlApplicationnContext
	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
			Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
			// 加載 BeanDefinition
			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;
	}
}

其實可以看到還是調用了參數爲 Resource 的 loadBeanDefinitions() 方法。那麼 Resource 到底是什麼呢?

Spring 的配置文件讀取是通過 ClassPathResource 進行封裝的,如 new ClassPathResource(“spring.xml”),那麼 ClassPathResource 完成了什麼功能呢?

在 java 中,將不同來源的資源抽象成 URI,通過註冊不同的 handler(URLStreamHandler)來處理不同來源的資源的讀取邏輯,一般 handler 的類型使用不同前綴(協議,Protocol)來識別,如 “file:”、“http:” 等,然而 URL 沒有默認定義相對 Classpath 或 ServletContext 等資源的 handler,雖然可以註冊自己的 URLStreamHandler 來解析特定的 URL 前綴(協議),然後這需要了解 URL 的實現機制,而且 URL 也沒有提供一些基本的方法,因而 Spring 對其內部使用到的資源實現了自己的抽象結構:Resource 接口來封裝底層資源。

public interface InputStreamSource {
  InputStream getInputStream() throws IOException;
}
public interface Resource extends InputStreamSource {
   boolean exists();
   boolean isReadable();
   boolean isOpen();
   URL getURL() throws IOException;
   URI getURI() throws IOException;
   File getFile() throws IOException;
   long contentLength() throws IOException;
   long lastModified() throws IOException;
   Resource createRelative(String var1) throws IOException;
   String getFilename();
   String getDescription();
}

有了 Resource 接口便可以對所有資源文件進行統一處理。
接下來看看 XMLBeanDefinitionReader 的 loadBeanDefinitions() 方法。

/**
 * Load bean definitions from the specified XML file.
 * @param resource the resource descriptor for the XML file
 * @return the number of bean definitions found
 * @throws BeanDefinitionStoreException in case of loading or parsing errors
 */
@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
	return loadBeanDefinitions(new EncodedResource(resource));
}

這裏 EncodeResource 實現了 InputStreamSource,它是對 Resource 進行封裝,設置文件編碼。

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
	// 獲取已經解析的資源
	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 {
		// 跟 xml 文件對應的輸入流
		InputStream inputStream = encodedResource.getResource().getInputStream();
		try {
			// 把流封裝爲可以解析 xml 文件的 sax 輸入源
			InputSource inputSource = new InputSource(inputStream);
			if (encodedResource.getEncoding() != null) {
				inputSource.setEncoding(encodedResource.getEncoding());
			}
			
			// 真正加載 BeanDefinition 的代碼入口
			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();
		}
	}
}

敲黑板啦,終於看到了真正解析 BeanDefinition 的代碼入口了。

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
			throws BeanDefinitionStoreException {
	try {
		// 獲取代表 xml 文件的 Document 對象(dom 解析 xml 文件)
		Document doc = doLoadDocument(inputSource, resource);
		// 解析 xml 文件並註冊 BeanDefinition
		return registerBeanDefinitions(doc, resource);
	} catch (Exception ignored) {
		// 省略對各種異常的捕獲
	}
}

到了這個地方纔可以說是把 Spring 對 BeanDefinition 的加載鏈路給捋清楚了,真正解析邏輯以及註冊留在下一篇博文。最後,用一張時序圖來總結下整個鏈路。加載 bean

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