Spring Ioc源碼分析系列--Ioc源碼入口分析

来源:https://www.cnblogs.com/codegitz/archive/2022/05/07/16243680.html
-Advertisement-
Play Games

Spring Ioc源碼分析系列--Ioc源碼入口分析 本系列文章代碼基於Spring Framework 5.2.x 前言 上一篇文章Spring Ioc源碼分析系列--Ioc的基礎知識準備介紹了Ioc的基礎概念以及Spring Ioc體系的部分基礎知識。那麼這一篇就會真正通過一個例子,啟動Ioc ...


Spring Ioc源碼分析系列--Ioc源碼入口分析

本系列文章代碼基於Spring Framework 5.2.x

前言

上一篇文章Spring Ioc源碼分析系列--Ioc的基礎知識準備介紹了Ioc的基礎概念以及Spring Ioc體系的部分基礎知識。那麼這一篇就會真正通過一個例子,啟動Ioc容器,獲取容器里的bean

首先說明,本文的例子是基於xml配置文件去完成的。

為什麼是xml因為xml是Spring的靈魂,可能我們初學Spring都會有畏難情緒,看到繁雜的xml就會打退堂鼓。但是實際上不然,xml的格式是相當清晰的,一個配置文件可以說沒有一行配置是多餘的。現在大部分的配置是用註解去完成的,相比xml而言是簡潔許多,但是對於我們初學而言,xml其實是更好的方式xml相對於註解而言是相對繁雜,但是它的信息也更多更明確,註解只是添加了一個註解就完成配置,細節上是更為隱蔽的。再加上xml配置文件和註解配置的原理是相通的,核心思想是一樣的,掌握核心就萬變不離其宗。所以這系列文章的例子大部分都會採取xml的方式去配置,當然後續可能也會補充一下註解方式的例子和分析文章。

萬事開頭難,如果實在覺得看不懂但又想學的,可以硬著頭皮看下去,等以後回過頭來再看的時候,會有豁然開朗的感覺。

源碼分析

啟動容器示例

廢話少說,下麵開始搞個例子分析一下。所有源碼都在我的倉庫ioc-sourcecode-analysis-code-demo里找到。

首先弄個實體類User

/**
 * @author Codegitz
 * @date 2022/4/26 10:58
 **/
public class User {
	private String id;

	private String name;

	private String age;
}

創建業務類UserService以及業務實現類UserServiceImpl,這裡的邏輯很簡單,就是根據傳入的nameage返回一個新的user對象。

/**
 * @author Codegitz
 * @date 2022/4/26 10:59
 **/
public interface UserService {
	User getUser(String name,String age);
}

public class UserServiceImpl implements UserService {
	@Override
	public User getUser(String name, String age) {
		User user = new User();
		user.setId("1");
		user.setName(name);
		user.setAge(age);
		return user;
	}
}

業務類的準備工作已經完成了,接下來就是要寫個xml配置文件,告訴Spring Ioc我需要一個UserServicebean

<?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="userService" class="io.codegitz.service.impl.UserServiceImpl"/>
</beans>

這個xml配置文件比較簡單,我們來解釋一下每一行是什麼意思。

<?xml version="1.0" encoding="UTF-8"?>為xml文件的規定頭,沒啥好說的。

xmlns="http://www.springframework.org/schema/beans"表明瞭xml的命名空間,xmlns全稱為xml namespace,一個xml裡面命名空間可以有多個。

xmlns:xsixsi:schemaLocation是指明瞭xml文件的驗證模型和驗證模型文件的位置。可以看到驗證模型能通過http方式獲取,但是為了防止網路抖動的影響,一般會在本地存放驗證文件。spring-beans.xsd就可以在圖示的目錄下找到。

1651890969660

接下來就到了<bean id="userService" class="io.codegitz.service.impl.UserServiceImpl"/>這一行,這是我們獲取一個Bean的關鍵配置,這裡告訴Ioc我需要一個iduserServiceclassio.codegitz.service.impl.UserServiceImpl的Bean。

到這裡配置已經完成了,接下來創建一個引導類啟動Ioc就能獲取到這個bean了。

新建引導類Application,通過ClassPathXmlApplicationContext類引導啟動Ioc容器,這是載入xml配置的常用引導類。

/**
 * @author Codegitz
 * @date 2022/4/26 10:57
 **/
public class Application {
	public static void main(String[] args) {
		ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
		UserService userService = (UserService) applicationContext.getBean("userService");
		User codegitz = userService.getUser("codegitz", "25");
		System.out.println(codegitz);
	}
}

到這裡其實已經完成了所有樣例的準備,可以啟動容器了。

1651893423083

可以看到,只是簡單的ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");就可以啟動一個容器,隨後就能通過getBean()方法獲取容器里的bean了,接下來我們看看new ClassPathXmlApplicationContext("beans.xml")發生了什麼。

容器啟動分析

ClassPathXmlApplicationContext構造方法

這裡我們使用的是ClassPathXmlApplicationContext,那麼就從這個類的構造方法開始分析。

摘取的代碼片段我會保留原文註釋,看官可以細細品味。

	/**
	 * Create a new ClassPathXmlApplicationContext, loading the definitions
	 * from the given XML file and automatically refreshing the context.
	 * @param configLocation resource location
	 * @throws BeansException if context creation failed
	 */
	public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
		this(new String[] {configLocation}, true, null);
	}

繼續跟進構造函數,根據方法上的註釋和代碼可以看到。這裡就做了兩件事,根據給定的xml配置文件載入BeanDefinition,然後調用refresh()方法刷新容器。

	/**
	 * Create a new ClassPathXmlApplicationContext with the given parent,
	 * loading the definitions from the given XML files.
	 * @param configLocations array of resource locations
	 * @param refresh whether to automatically refresh the context,
	 * loading all bean definitions and creating all singletons.
	 * Alternatively, call refresh manually after further configuring the context.
	 * @param parent the parent context
	 * @throws BeansException if context creation failed
	 * @see #refresh()
	 */
	public ClassPathXmlApplicationContext(
			String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
			throws BeansException {

		// 初始化父類
		super(parent);
		// 設置配置文件信息,這裡會進行占位符的替換
		// 例如/${env}-beans.xml類型的路徑會被占位符替換,如果env=sit,那麼路徑就會被替換為/sit-beans.xml
		setConfigLocations(configLocations);
		// 如果沒有刷新,就會調用refresh()方法進行刷新,這個方法是整個Ioc的關鍵入口,由父類AbstractApplicationContext實現
		if (refresh) {
			refresh();
		}
	}

前面兩個方法比較簡單,可以跟著註釋看一下。重點在refresh()方法。這個方法會完成Ioc所需要的所有配置載入,完成所有bean的註冊以及Spring的一系列實現。

refresh()方法

到這裡,才是真正摸到了Ioc的源碼入口。看一下refresh()方法的代碼。

	public void refresh() throws BeansException, IllegalStateException {
		//給容器refresh加鎖,避免容器在refresh階段時,容器進行了初始化或者銷毀操作
		synchronized (this.startupShutdownMonitor) {
			// Prepare this context for refreshing.
			//調用容器準備刷新的方法,獲取容器當前時間,同時給容器設置同步標識、具體方法
			prepareRefresh();

			// Tell the subclass to refresh the internal bean factory.
			/**
			 * 告訴子類啟動refreshBeanFactory方法,refreshBeanFactory方法會載入bean的定義文件,該方法的實現會針對xml配置,最終創建內部容器
			 * 該容器負責bean的創建與管理,會進行BeanDefinition的註冊
			 */
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			// Prepare the bean factory for use in this context.
			//註冊一些容器中需要使用的系統bean,例如classloader,BeanFactoryPostProcessor等
			prepareBeanFactory(beanFactory);

			try {
				// Allows post-processing of the bean factory in context subclasses
				// 允許容器的子類去註冊PostProcessor,鉤子方法.
				postProcessBeanFactory(beanFactory);

				// Invoke factory processors registered as beans in the context.
				//激活容器中註冊為bean的BeanFactoryPostProcessor
				//對於註解容器,org.springframework.context.annotation.ConfigurationClassPostProcessor
				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();
			}
		}
	}

咋一看代碼量非常大,但是邏輯是比較清晰的,結合註釋來看,再調試一遍,沒啥大問題。

下麵對方法進行逐個分析,鑒於一次性寫完會比較長,本文先分析前三個方法,即prepareRefresh()obtainFreshBeanFactory()prepareBeanFactory(beanFactory)三個方法。

prepareRefresh()方法

跟進prepareRefresh()方法,這個方法主要是做了一些初始化屬性設置和校驗。

	/**
	 * Prepare this context for refreshing, setting its startup date and
	 * active flag as well as performing any initialization of property sources.
	 */
	protected void prepareRefresh() {
		// Switch to active.
		this.startupDate = System.currentTimeMillis();
		this.closed.set(false);
		this.active.set(true);
        
        // 省略部分日誌...

		// Initialize any placeholder property sources in the context environment.
		// 初始化一些屬性,交由子類實現
		initPropertySources();

		// Validate that all properties marked as required are resolvable:
		// see ConfigurablePropertyResolver#setRequiredProperties
		// 校驗是否有必須的屬性,如果有必須的屬性但是環境沒配置,則拋出異常
		getEnvironment().validateRequiredProperties();

		// Store pre-refresh ApplicationListeners...
		// 存儲在refresh之前註冊的ApplicationListener,避免這部分的ApplicationListener在後續的調用中被丟失
		// 這是一個2019年修複的bug
		if (this.earlyApplicationListeners == null) {
			this.earlyApplicationListeners = new LinkedHashSet<>(this.applicationListeners);
		}
		else {
			// Reset local application listeners to pre-refresh state.
			this.applicationListeners.clear();
			this.applicationListeners.addAll(this.earlyApplicationListeners);
		}

		// Allow for the collection of early ApplicationEvents,
		// to be published once the multicaster is available...
		this.earlyApplicationEvents = new LinkedHashSet<>();
	}

這個裡面比較簡單,沒啥好看。稍微來看下getEnvironment().validateRequiredProperties()的代碼。

	/**
	 * Return the {@code Environment} for this application context in configurable
	 * form, allowing for further customization.
	 * <p>If none specified, a default environment will be initialized via
	 * {@link #createEnvironment()}.
	 */
	@Override
	public ConfigurableEnvironment getEnvironment() {
		if (this.environment == null) {
			this.environment = createEnvironment();
		}
		return this.environment;
	}

	/**
	 * Create and return a new {@link StandardEnvironment}.
	 * <p>Subclasses may override this method in order to supply
	 * a custom {@link ConfigurableEnvironment} implementation.
	 */
	protected ConfigurableEnvironment createEnvironment() {
		return new StandardEnvironment();
	}

可以看到這裡返回的是StandardEnvironment類型Environment,這裡的邏輯就是有則返回,無則創建。接下來看validateRequiredProperties()方法。最終實現是在AbstractPropertyResolver#validateRequiredProperties()方法。

	@Override
	public void validateRequiredProperties() {
		MissingRequiredPropertiesException ex = new MissingRequiredPropertiesException();
		// 遍歷requiredProperties,逐個去當前環境獲取是否存在
		for (String key : this.requiredProperties) {
			if (this.getProperty(key) == null) {
				ex.addMissingRequiredProperty(key);
			}
		}
		// 如果存在缺失的必須屬性,則拋出異常
		if (!ex.getMissingRequiredProperties().isEmpty()) {
			throw ex;
		}
	}

prepareRefresh()方法比較簡單,到此已經基本看完,接下來看下一個obtainFreshBeanFactory()方法。

obtainFreshBeanFactory()方法

obtainFreshBeanFactory()方法從名字上就知道是獲取並且同時刷新一個beanfactory

在沒有分析代碼之前,先來猜測一下它的實現。我們知道它最終會返回一個可以使用的beanfactory,說明此時在beanfactory里已經初始化完成了我們定義的BeanDefinition,那麼這裡應該會有一些基礎信息的配置,然後解析xml文件,獲取xml配置信息,然後初始化相應的BeanDefinition,準備就緒後返回beanfactory。當然這隻是猜測,具體實現如何,馬上分析。

那麼接下來就跟進這個方法看一下代碼實現。

	/**
	 * Tell the subclass to refresh the internal bean factory.
	 * @return the fresh BeanFactory instance
	 * @see #refreshBeanFactory()
	 * @see #getBeanFactory()
	 */
	protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
		// 調用子類的實現,刷新beanFactory
		refreshBeanFactory();
		// 返回刷新完成(即啟動完成)的beanFactory
		return getBeanFactory();
	}

跟進refreshBeanFactory()方法,關鍵地方都已經加上註釋,也比較簡單易懂,跟著看一下就行。

	/**
	 * 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.
	 * 此實現執行此上下文的底層 bean 工廠的實際刷新,
	 * 關閉先前的 bean 工廠(如果有)併為上下文生命周期的下一階段初始化一個新的 bean 工廠。
	 */
	@Override
	protected final void refreshBeanFactory() throws BeansException {
		// 如果有前一個,先關閉
		if (hasBeanFactory()) {
			// 先銷毀所有已經註冊的bean
			destroyBeans();
			// 關閉工廠,將this.beanFactory設為null
			closeBeanFactory();
		}
		try {
			//創建DefaultListableBeanFactory
			DefaultListableBeanFactory beanFactory = createBeanFactory();
			//為了序列化指定id,如果需要的話,讓這個beanFactory從id反序列化到BeanFactory對象
			beanFactory.setSerializationId(getId());
			//定製beanFactory,設置相關屬性,包括是否允許覆蓋同名稱的不同定義的對象已經迴圈依賴
			//以及設置@Autowire和@Qualifier註冊解析器QualifierAnnotationAutowire-CandidateResolver
			customizeBeanFactory(beanFactory);
			//初始化DocumentReader,併進行xml文件讀取和解析
			loadBeanDefinitions(beanFactory);
			synchronized (this.beanFactoryMonitor) {
				this.beanFactory = beanFactory;
			}
		}
		catch (IOException ex) {
			throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
		}
	}

前面的準備工作是比較簡單的,不再細說。重點在loadBeanDefinitions(beanFactory)方法,這裡會載入xml獲取我們配置的BeanDefinition

記住這個位置AbstractRefreshableApplicationContext#refreshBeanFactory(),這是經過前面繁雜的摸索後真正進入BeanDefinition載入的分水嶺。

1651909478106

跟進代碼查看。

	/**
	 * Loads the bean definitions via an XmlBeanDefinitionReader.
	 * 通過 XmlBeanDefinitionReader 載入 bean 定義。
	 * @see org.springframework.beans.factory.xml.XmlBeanDefinitionReader
	 * @see #initBeanDefinitionReader
	 * @see #loadBeanDefinitions
	 */
	@Override
	protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
		// Create a new XmlBeanDefinitionReader for the given BeanFactory.
		//為指定 beanFactory創建XmlBeanDefinitionReader
		XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

		// Configure the bean definition reader with this context's
		// resource loading environment.
		//對 beanDefinitionReader 進行環境變數的設置
		beanDefinitionReader.setEnvironment(this.getEnvironment());
		beanDefinitionReader.setResourceLoader(this);
		beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));

		// Allow a subclass to provide custom initialization of the reader,
		// then proceed with actually loading the bean definitions.
		// 允許子類提供閱讀器的自定義初始化,然後繼續實際載入 bean 定義。
		// 對 beanDefinitionReader 進行屬性設置
		initBeanDefinitionReader(beanDefinitionReader);
		// 載入 bean 定義
		loadBeanDefinitions(beanDefinitionReader);
	}

可以看到這裡也是進行了一波準備工作,首先是給beanFactory創建一個XmlBeanDefinitionReader,後續xml解析都是由它來完成,接下來對這個XmlBeanDefinitionReader進行一些屬性設置,接下來調用loadBeanDefinitions(beanDefinitionReader)載入BeanDefinition,跟進代碼查看。

	/**
	 *
	 * 使用給定的 XmlBeanDefinitionReader 載入 bean 定義。
	 * <p>bean 工廠的生命周期由 {@link refreshBeanFactory} 方法處理;因此這個方法只是應該載入和或註冊 bean 定義。
	 *
	 * 在初始化了DefaultListableBeanFactory和XmlBeanDefinitionReader後就可以進行配置文件的讀取了
	 *
	 */
	protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
		// 返回一個引用構建此上下文的 XML bean 定義文件的 Resource 對象數組, 預設實現返回 {@code null}。
		// 子類可以覆蓋它以提供預構建的資源對象而不是占位符字元串。
		Resource[] configResources = getConfigResources();
		if (configResources != null) {
			reader.loadBeanDefinitions(configResources);
		}
		// 返回引用構建此上下文的 XML bean 定義文件資源位置數組,還可以包括占位符模式,這將通過 ResourcePatternResolver 進行解析獲取。
		// <p>預設實現返回 {@code null}。子類可以覆蓋它以提供一組資源位置以從中載入 bean 定義。
		String[] configLocations = getConfigLocations();
		if (configLocations != null) {
			reader.loadBeanDefinitions(configLocations);
		}
	}

可以看到,由於沒有預構建的資源,所以configResources會為nullconfigLocations則獲取到了我們的beans.xml,那麼接下來就會解析beans.xml獲取BeanDefinition

1651910565636

跟進reader.loadBeanDefinitions(configLocations)方法,具體的實現是在 AbstractBeanDefinitionReader#loadBeanDefinitions(String... locations)方法。

	@Override
	public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
		Assert.notNull(locations, "Location array must not be null");
		int count = 0;
		for (String location : locations) {
			count += loadBeanDefinitions(location);
		}
		return count;
	}

繼續套娃,進入loadBeanDefinitions(location)方法。

	public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
		//獲取資源載入器,主要功能是根據路徑和類載入器獲取Resource對象
		ResourceLoader resourceLoader = getResourceLoader();
		if (resourceLoader == null) {
			throw new BeanDefinitionStoreException(
					"Cannot load bean definitions from location [" + location + "]: no ResourceLoader available");
		}

		//ResourcePatternResolver用於載入多個文件或者載入Ant風格路徑的資源文件
		if (resourceLoader instanceof ResourcePatternResolver) {
			// Resource pattern matching available.
			try {
				// 以Resource形式返回所有配置文件
				Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
				// 通過resources載入
				int count = loadBeanDefinitions(resources);
				if (actualResources != null) {
					Collections.addAll(actualResources, resources);
				}
				if (logger.isTraceEnabled()) {
					logger.trace("Loaded " + count + " bean definitions from location pattern [" + location + "]");
				}
				return count;
			}
			catch (IOException ex) {
				throw new BeanDefinitionStoreException(
						"Could not resolve bean definition resource pattern [" + location + "]", ex);
			}
		}
		else {
			/**
			 * 載入單個資源,直接使用ResourceLoader載入
			 */
			// Can only load single resources by absolute URL.
			Resource resource = resourceLoader.getResource(location);
			int count = loadBeanDefinitions(resource);
			if (actualResources != null) {
				actualResources.add(resource);
			}
			if (logger.isTraceEnabled()) {
				logger.trace("Loaded " + count + " bean definitions from location [" + location + "]");
			}
			return count;
		}
	}

這裡會經過一系列的重載方法,最終會來到doLoadBeanDefinitions()方法里,這才是真正解析的地方,好家伙,山路十八彎。

1651915020314

跟進doLoadBeanDefinitions()方法。

	/**
	 * Actually load bean definitions from the specified XML file.
	 * @param inputSource the SAX InputSource to read from
	 * @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
	 * @see #doLoadDocument
	 * @see #registerBeanDefinitions
	 */
	protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
			throws BeanDefinitionStoreException {

		try {
			/**
			 * 創建Document對象,XML文檔對象,即dom樹
			 * 使用這個Document對象可以獲取XML文件中的節點並且創建節點
			 * SAX XML
			 * 解析dom樹,即解析出一個個屬性,將其屬性保存到BeanDefinition當中
			 * 並向容器註冊BeanDefinition
			 */
			Document doc = doLoadDocument(inputSource, resource);
			int count = registerBeanDefinitions(doc, resource);
			if (logger.isDebugEnabled()) {
				logger.debug("Loaded " + count + " bean definitions from " + resource);
			}
			return count;
		}
		catch (BeanDefinitionStoreException ex) {
			throw ex;
		}
		// 省略部分異常信息
	}

獲取Document對象就不說了,可以單獨出一篇文章Spring Ioc源碼分析系列--獲取xml文件Document對象,這裡再跟進去就離主題太遠了。

跟進registerBeanDefinitions(doc, resource)方法。

	public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
		//創建BeanDefinitionDocumentReader,這個是實際從XML的dom樹中服務BeanDefinition
		BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
		//獲取註冊表BeanDefinitionMap在本次載入前的BeanDefinition數量
		int countBefore = getRegistry().getBeanDefinitionCount();
		//載入並註冊
		documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
		//用本次載入後的數據減去以前有的數量,即為本次載入的BeanDefinition數量
		return getRegistry().getBeanDefinitionCount() - countBefore;
	}

重點在方法documentReader.registerBeanDefinitions(doc, createReaderContext(resource))里,跟進代碼。

	protected void doRegisterBeanDefinitions(Element root) {
		// Any nested <beans> elements will cause recursion in this method. In
		// order to propagate and preserve <beans> default-* attributes correctly,
		// keep track of the current (parent) delegate, which may be null. Create
		// the new (child) delegate with a reference to the parent for fallback purposes,
		// then ultimately reset this.delegate back to its original (parent) reference.
		// this behavior emulates a stack of delegates without actually necessitating one.
		//BeanDefinition的解析委托類
		BeanDefinitionParserDelegate parent = this.delegate;
		//判斷這個根節點是否是預設的命名空間
		//底層就是判斷這個根節點的namespace="http://www.springframework.org/schema/beans"
		this.delegate = createDelegate(getReaderContext(), root, parent);

		if (this.delegate.isDefaultNamespace(root)) {
			//獲取這個profile的值,表示剖面,用於設置環境
			String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
			if (StringUtils.hasText(profileSpec)) {
				//根據分隔符換成數組
				String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
						profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
				// We cannot use Profiles.of(...) since profile expressions are not supported
				// in XML config. See SPR-12458 for details.
				//判斷這個切麵是否是激活的環境,如果不是直接返回,表示這個配置文件不是當前運行環境的配置文件
				if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
					if (logger.isDebugEnabled()) {
						logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec +
								"] not matching: " + getReaderContext().getResource());
					}
					return;
				}
			}
		}

		//解析XML之前做的準備工作,其實什麼也沒做
		preProcessXml(root);
		//調用這個方法解析
		parseBeanDefinitions(root, this.delegate);
		//後續處理
		postProcessXml(root);

		this.delegate = parent;
	}

跟進解析BeanDefinition的方法parseBeanDefinitions(root, this.delegate)裡面。

	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;
					//Bean定義的document對象使用了spring的預設命名空間,如http://www.springframework.org/schema/beans
					if (delegate.isDefaultNamespace(ele)) {
						//若是則按照spring原有的邏輯進行解析
						parseDefaultElement(ele, delegate);
					}
					else {
						delegate.parseCustomElement(ele);
					}
				}
			}
		}
		else {
			//使用擴展的自定義代理類去解析
			delegate.parseCustomElement(root);
		}
	}

這個例子沒有自定義標簽,進入到預設標簽的解析。

	//根據不同的標簽進行解析
	private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
		//如果是import標簽,進入導入解析
		if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
			importBeanDefinitionResource(ele);
		}
		//若果是別名元素,則進行別名解析
		else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
			processAliasRegistration(ele);
		}
		//bean元素解析
		else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
			processBeanDefinition(ele, delegate);
		}
		else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
			// recurse
			doRegisterBeanDefinitions(ele);
		}
	}

顯然我們的例子只有一個Bean標簽,所以會進入到processBeanDefinition()方法里。

	/**
	 * Process the given bean element, parsing the bean definition
	 * and registering it with the registry.
	 */
	protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
		//BeanDefinitionHolder是對BeanDefinition的封裝,即bean定義的封裝類
		//對document對象中的bean標簽解析由BeanDefinitionParserDelegate實現
		BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
		if (bdHolder != null) {
			bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
			try {
				// Register the final decorated instance.
				//從springIOC容器註冊解析得到的BeanDefinition,這是BeanDefinition向IOC容器註冊的入口
				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()方法真正將BeanDefinition註冊進容器里。咋註冊呢?其實就是加到BeanFactorybeanDefinitionMap屬性里。beanDefinitionMap可以說就是BeanDefinition的容器了。

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

		// Register bean definition under primary name.
		String beanName = definitionHolder.getBeanName();
		// 這裡真正把BeanDefinition註冊到了BeanFactory里
		registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

		// Register aliases for bean name, if any.
		// 註冊別名
		String[] aliases = definitionHolder.getAliases();
		if (aliases != null) {
			for (String alias : aliases) {
				registry.registerAlias(beanName, alias);
			}
		}
	}

到這裡已經完成了BeanDefinition的註冊,是不是有點曲折?其實很簡單,就是細節比較多

到這裡obtainFreshBeanFactory()方法已經基本結束,已經完成了配置文件的解析並且註冊BeanDefinition的過程了。剩下的操作就是把這個BeanFactory返回給上一步。

prepareBeanFactory()方法

接下來分析一下prepareBeanFactory()方法,這方法也簡單,主要就是對BeanFactory進行一些屬性的設置,跟著註釋看一下就行。

	/**
	 * Configure the factory's standard context characteristics,
	 * such as the context's ClassLoader and post-processors.
	 * @param beanFactory the BeanFactory to configure
	 */
	protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
		// Tell the internal bean factory to use the context's class loader etc.
		// 設置 beanFactory的classLoader 為當前 context的classLoader
		beanFactory.setBeanClassLoader(getClassLoader());
		/**
		 * 設置 beanFactory的表達式語言處理器,Spring3 增加了表達式語言的支持,
		 * 預設可以使用#{bean.xxx}的形式來調用相關屬性值
		 * @Qusetion 在註冊了這個解析器之後,spring是在什麼時候調用這個解析器的呢?
		 * Spring在bean進行初始化的時候會有屬性填充的步驟,而在這一步中
		 * Spring會調用AbstractAutowireCapableBeanFactory類的applyPropertyValues函數來完成功能。
		 * 就這個函數中,會通過構造 BeanDefinitionValueResolver類型實例valueResolver來進行屬性值的解析。
		 * 同時,也是在這個步驟中一般通過 AbstractBeanFactory 中的 evaluateBeanDefinitionString
		 * 方法去完成SpEL解析
		 */
		beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver(beanFactory.getBeanClassLoader()));
		//為beanFactory增加一個PropertyEditor,這個主要是對bean的屬性等設置管理的一個工具
		beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment()));

		// Configure the bean factory with context callbacks.
		//添加beanPostProcessor
		beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));
		//設置幾個忽略自動裝配的介面
		// aware都是由invokeAware方法註入,忽略自動Autowire
		beanFactory.ignoreDependencyInterface(EnvironmentAware.class);
		beanFactory.ignoreDependencyInterface(EmbeddedValueResolverAware.class);
		beanFactory.ignoreDependencyInterface(ResourceLoaderAware.class);
		beanFactory.ignoreDependencyInterface(ApplicationEventPublisherAware.class);
		beanFactory.ignoreDependencyInterface(MessageSourceAware.class);
		beanFactory.ignoreDependencyInterface(ApplicationContextAware.class);

		// BeanFactory interface not registered as resolvable type in a plain factory.
		// MessageSource registered (and found for autowiring) as a bean.
		// 設置了幾個自動裝配的規則,後面三個都是把當前對象註冊了進去,只有BeanFactory老老實實得註冊了一個BeanFactory
		beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory);
		beanFactory.registerResolvableDependency(ResourceLoader.class, this);
		beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this);
		beanFactory.registerResolvableDependency(ApplicationContext.class, this);

		// Register early post-processor for detecting inner beans as ApplicationListeners.
		beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(this));

		// Detect a LoadTimeWeaver and prepare for weaving, if found.
		// 添加對AspectJ的支持
		if (beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
			beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
			// Set a temporary ClassLoader for type matching.
			beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
		}

		// Register default environment beans.添加系統環境預設的bean
		if (!beanFactory.containsLocalBean(ENVIRONMENT_BEAN_NAME)) {
			beanFactory.registerSingleton(ENVIRONMENT_BEAN_NAME, getEnvironment());
		}
		if (!beanFactory.containsLocalBean(SYSTEM_PROPERTIES_BEAN_NAME)) {
			beanFactory.registerSingleton(SYSTEM_PROPERTIES_BEAN_NAME, getEnvironment().getSystemProperties());
		}
		if (!beanFactory.containsLocalBean(SYSTEM_ENVIRONMENT_BEAN_NAME)) {
			beanFactory.registerSingleton(SYSTEM_ENVIRONMENT_BEAN_NAME, getEnvironment().getSystemEnvironment());
		}
	}

到這裡refresh()的前面三個方法已經簡單過完了,除瞭解析配置文件複雜點,其他的都是些屬性配置居多。

小結

本文先通過一個列子驅動,找到了Ioc容器啟動的入口,簡單分析了一下前三個方法,最重要的就是載入BeanDefinition的過程了,可以仔細看多幾遍。

今天發生了點小插曲,本該寫得更詳細點的,但是寫不了了,只能作罷,將就著看吧。

如果有人看到這裡,那在這裡老話重提。與君共勉,路漫漫其修遠兮,吾將上下而求索。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 今天的內容有意思了,朋友們繼續對我們之前的案例完善,是這樣的我們之前是不是靠props來完成父給子,子給父之間傳數據,其實父給子最好的方法就是props但是自給父就不是了,並且今天學下來,不僅如此,組件間任何層級的關係我都可以傳數據了,兄弟之間,爺孫之間等等等等 七.瀏覽器本地存儲 1.localS ...
  • 移動端瀑布流佈局是一種比較流行的網頁佈局方式,視覺上來看就是一種像瀑布一樣垂直落下的排版。每張圖片並不是顯示的正正方方的,而是有的長有的短,呈現出一種不規則的形狀。但是它們的寬度通常都是相同的 因為移動端瀑布流佈局主要為豎向瀑布流,因此本文所探討的是豎向瀑布流 特點 豎向瀑布流佈局主要有下麵幾種特點 ...
  • 可以將( 0, null, false, undefined, NaN )理解為數字 0 與運算: 與運算 類比四則運算中的乘法。0和任何數相乘都等於0,因此他們和其他值做與運算都等於0(等於他本身,例如:null && 'abc',結果為 null;1414 && 0,結果為 0)。 若是兩個0 ...
  • 線程和進程是電腦操作系統的基礎概念,在程式員中屬於高頻辭彙,那如何理解呢?Node.js 中的進程和線程又是怎樣的呢? 一、進程和線程 1.1、專業性文字定義 進程(Process),進程是電腦中的程式關於某數據集合上的一次運行活動,是系統進行資源分配和調度的基本單位,是操作系統結構的基礎,進程 ...
  • 重要性 有過一些實際開發工作的朋友一定對某個場景會深有體會,那就是客戶經常會對現有的功能提出新的需求要我們改動,並且要快速完成。如果你的代碼沒有很好的遵循“開閉原則”,並且頂著工期的縮減,那我們對需求變化的修改,“往往就像在一個草稿紙上反覆的塗抹”,隨著不斷的變化修改代碼就會顯得很亂,可能到最後你連 ...
  • 首先先介紹三個性質 可見性 可見性代表主記憶體中變數更新,線程中可以及時獲得最新的值。 下麵例子證明瞭線程中可見性的問題 由於發現多次執行都要到主記憶體中取變數,所以會將變數緩存到線程的工作記憶體,這樣當其他線程更新該變數的時候,該線程無法得知,導致該線程會無限的運行下去。 public class te ...
  • 寫在前面,本文主要介紹Python基礎排序和自定義排序的一些規則,如果都比較熟悉,可以直接翻到第三節,看下實際的筆試面試題中關於自定義排序的應用。 一、基礎排序 排序是比較基礎的演算法,與很多語言一樣,Python也提供了對列表的排序方法和內建排序函數。 1、兩種排序方式 方式一: li = [1, ...
  • 第一個Python程式 每個編程語言的學習,第一個程式都是先向世界問好,Python 也不例外,這節我們先寫下第一個Python 程式 —— Hello World 。 一、Python 簡介 Python 是著名的“龜叔” Guido van Rossum 在 1989 年聖誕節期間,為了打發無聊 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...