Spring原始碼教程02--Spring的IoC容器分析

weir發表於2018-01-28

上一節“[原創]Spring教程01--Spring開始篇_Helloworld”中簡單的介紹SpringFramwork的簡單使用;通過Helloworld的程式做演示,本節將繼續解讀Spring的Ioc容器實現和分析。

Spring IoC容器啟動過程

Spring的IoC容器啟動大致分為下面的三個步驟:Resource定位Resoure的載入Resoured的註冊 三個步驟;下面使用xml檔案配置方式顯示的配置方式步驟:

啟動過程

首先看下ApplicationContext的構造方法定義:

public FileSystemXmlApplicationContext(
		String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
		throws BeansException {
    
    //設定Context
	super(parent);
	//讀取XML等配置檔案定位
	setConfigLocations(configLocations);
	//建立BeanFactory的關鍵[重點]
	if (refresh) {
		refresh();
	}
}


@Override
//這個refresh()啟動容器[重要的放在首位]
public void refresh() throws BeansException, IllegalStateException {
	synchronized (this.startupShutdownMonitor) {
		// Prepare this context for refreshing.
		prepareRefresh();

		// Tell the subclass to refresh the internal bean factory.
		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();
		}
	}
}
複製程式碼

Resource定位

為了便於理解;提前解析了Context的屬性如下:

物件名 類 型 作 用 歸屬類
configResources Resource[] 配置檔案資源物件陣列 ClassPathXmlApplicationContext
configLocations String[] 配置檔案字串陣列,儲存配置檔案路徑 AbstractRefreshableConfigApplicationContext
beanFactory DefaultListableBeanFactory 上下文使用的Bean工廠 AbstractRefreshableApplicationContext
beanFactoryMonitor Object Bean工廠使用的同步監視器 AbstractRefreshableApplicationContext
id String 上下文使用的唯一Id,標識此ApplicationContext AbstractApplicationContext
parent ApplicationContext 父級ApplicationContext AbstractApplicationContext
beanFactoryPostProcessors List 儲存BeanFactoryPostProcessor介面,Spring提供的一個擴充套件點 AbstractApplicationContext
startupShutdownMonitor Object refresh方法和destory方法公用的一個監視器,避免兩個方法同時執行 AbstractApplicationContext
shutdownHook Thread Spring提供的一個鉤子,JVM停止執行時會執行Thread裡面的方法 AbstractApplicationContext
resourcePatternResolver ResourcePatternResolver 上下文使用的資源格式解析器 AbstractApplicationContext
lifecycleProcessor LifecycleProcessor 用於管理Bean生命週期的生命週期處理器介面 AbstractApplicationContext
messageSource MessageSource 用於實現國際化的一個介面 AbstractApplicationContext
applicationEventMulticaster ApplicationEventMulticaster Spring提供的事件管理機制中的事件多播器介面 AbstractApplicationContext
applicationListeners Set Spring提供的事件管理機制中的應用監聽器 AbstractApplicationContext

1)設定儲存配置檔案路徑

AbstractRefreshableConfigApplicationContext 的 setConfigLocations() 方法確定其檔案的位置;程式碼比較簡單。

public void setConfigLocations(@Nullable String... locations) {
	if (locations != null) {
		Assert.noNullElements(locations, "Config locations must not be null");
		this.configLocations = new String[locations.length];
		for (int i = 0; i < locations.length; i++) {
			this.configLocations[i] = resolvePath(locations[i]).trim();
		}
	}
	else {
		this.configLocations = null;
	}
}
複製程式碼

在上面的過程;我們獲取配置檔案的路徑內容。下面看看具體的IoC容器的建立的具體過程了。

2)載入配置檔案的路徑

方法堆疊資訊

(1)建立BeanFactory

@Override
protected final void refreshBeanFactory() throws BeansException {
	if (hasBeanFactory()) {
		destroyBeans();
		closeBeanFactory();
	}
	try {
		DefaultListableBeanFactory beanFactory = createBeanFactory();
		beanFactory.setSerializationId(getId());
		customizeBeanFactory(beanFactory);
		loadBeanDefinitions(beanFactory);
		synchronized (this.beanFactoryMonitor) {
			this.beanFactory = beanFactory;
		}
	}
	catch (IOException ex) {
		throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
	}
}
複製程式碼

(2)讀取檔案配置 這個中間呼叫過程省略了;直接看結果:這裡直接看log日誌結果我們不用說太多了...

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<>(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());
			}
			//下面開始解析XML方法
			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();
		}
	}
}
複製程式碼

Resource的載入

下面我們接著根據路徑配置,讀取到記憶體中;採用SAX檔案的解析;看原始碼:

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
	throws BeanDefinitionStoreException {
    ...
try {
	Document doc = doLoadDocument(inputSource, resource);
	//解析xml檔案註冊JavaBean
	return registerBeanDefinitions(doc, resource);
}
    ...
    //省略catch()程式碼...
複製程式碼

Resouce檔案的解析

方法堆疊

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(specifiedProfiles)) {
				if (logger.isInfoEnabled()) {
					logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec +
							"] not matching: " + getReaderContext().getResource());
				}
				return;
			}
		}
	}

	preProcessXml(root);
	//重點程式碼:註冊JavaBean
	parseBeanDefinitions(root, this.delegate);
	postProcessXml(root);

	this.delegate = parent;
}
複製程式碼

其實寫到這裡感覺大家有興趣的可以繼續看看這個;如何讀取xml檔案如理 <bean> 等標籤的操作...這裡我推薦一片文章吧: Spring解密 - XML解析 與 Bean註冊;後面寫的比較具體。截止到目前通過XML解析;將配置檔案讀取到BeanDefine中;下面看看如何將其註冊到IoC容器。

註冊IoC容器

Spring原始碼教程02--Spring的IoC容器分析

	@Override
	public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
			throws BeanDefinitionStoreException {

		Assert.hasText(beanName, "Bean name must not be empty");
		Assert.notNull(beanDefinition, "BeanDefinition must not be null");

		if (beanDefinition instanceof AbstractBeanDefinition) {
			try {
				((AbstractBeanDefinition) beanDefinition).validate();
			}
			catch (BeanDefinitionValidationException ex) {
				throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
						"Validation of bean definition failed", ex);
			}
		}

		BeanDefinition oldBeanDefinition;

		oldBeanDefinition = this.beanDefinitionMap.get(beanName);
		if (oldBeanDefinition != null) {
			if (!isAllowBeanDefinitionOverriding()) {
				throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
						"Cannot register bean definition [" + beanDefinition + "] for bean '" + beanName +
						"': There is already [" + oldBeanDefinition + "] bound.");
			}
			else if (oldBeanDefinition.getRole() < beanDefinition.getRole()) {
				// e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
				if (this.logger.isWarnEnabled()) {
					this.logger.warn("Overriding user-defined bean definition for bean '" + beanName +
							"' with a framework-generated bean definition: replacing [" +
							oldBeanDefinition + "] with [" + beanDefinition + "]");
				}
			}
			else if (!beanDefinition.equals(oldBeanDefinition)) {
				if (this.logger.isInfoEnabled()) {
					this.logger.info("Overriding bean definition for bean '" + beanName +
							"' with a different definition: replacing [" + oldBeanDefinition +
							"] with [" + beanDefinition + "]");
				}
			}
			else {
				if (this.logger.isDebugEnabled()) {
					this.logger.debug("Overriding bean definition for bean '" + beanName +
							"' with an equivalent definition: replacing [" + oldBeanDefinition +
							"] with [" + beanDefinition + "]");
				}
			}
			//將獲取BeanName和之前解析BeanDefine放入Map集合中從而完成註冊
			this.beanDefinitionMap.put(beanName, beanDefinition);
		}
		else {
			if (hasBeanCreationStarted()) {
				// Cannot modify startup-time collection elements anymore (for stable iteration)
				synchronized (this.beanDefinitionMap) {
					this.beanDefinitionMap.put(beanName, beanDefinition);
					List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
					updatedDefinitions.addAll(this.beanDefinitionNames);
					updatedDefinitions.add(beanName);
					this.beanDefinitionNames = updatedDefinitions;
					if (this.manualSingletonNames.contains(beanName)) {
						Set<String> updatedSingletons = new LinkedHashSet<>(this.manualSingletonNames);
						updatedSingletons.remove(beanName);
						this.manualSingletonNames = updatedSingletons;
					}
				}
			}
			else {
				// Still in startup registration phase
				this.beanDefinitionMap.put(beanName, beanDefinition);
				this.beanDefinitionNames.add(beanName);
				this.manualSingletonNames.remove(beanName);
			}
			this.frozenBeanDefinitionNames = null;
		}

		if (oldBeanDefinition != null || containsSingleton(beanName)) {
			resetBeanDefinition(beanName);
		}
	}
複製程式碼

上面的原始碼中我們呢需要關注this.beanDefinitionMap.put(beanName, beanDefinition);這句程式碼;這個Bean注入BeanDefineMap中;到此這裡已經完成了IoC容器的初始化。

到這裡基本已經結束了;ApplicationContext application = new ClassPathXmlApplicationContext("applicationContext.xml");這樣一句程式碼其實;也寫了一章節;其實如果你是看著篇文章可以參考我的方法堆疊進行閱讀。最後文章的部分其實源都簡明的展示給你;後期我希望可以使用更多圖展示給大家。


參考資料:

相關文章