接着上篇,在根上下文初始化的過程中,有一步配置和啓動根上下文方法:org.springframework.web.context.ContextLoader#configureAndRefreshWebApplicationContext,這裏麪包含了對bean的所有處理,下面我們慢慢來看。
先看下這個方法的實現:configureAndRefreshWebApplicationContext
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
if (idParam != null) {
wac.setId(idParam);
}
else {
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(sc.getContextPath()));
}
}
wac.setServletContext(sc);
//讀取web.xm中配設置的contextConfigLocation參數值
String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
if (configLocationParam != null) {
wac.setConfigLocation(configLocationParam);
}
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
}
customizeContext(sc, wac);
//根上下文(ioc容器)的啓動,bean的處理也在這裏
wac.refresh();
}
然後進入refresh()方法:
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
//準備啓動上下文,設置開始時間,標記活動標誌,初始化配置文件中的佔位符
prepareRefresh();
//將 bean 定義加載到給定的 BeanFactory 中
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
//準備 BeanFactory 以便在此上下文中使用。
// 1. 設置 BeanFactory 的類加載器
// 2. 添加幾個 BeanPostProcessor
// 3. 實例化幾個特殊的 bean
prepareBeanFactory(beanFactory);
try {
//爲空實現,留給子類做擴展,不同 ApplicationContext 實現不同
postProcessBeanFactory(beanFactory);
// Spring 的 SPI
// 先調用 BeanDefinitionRegistryPostProcessor 和 ImportBeanDefinitionRegistrar 的實現類
// 再調用 BeanFactoryPostProcessor 各個實現類的 postProcessBeanFactory(factory) 方法
// 例如:ConfigurationClassPostProcessor 會掃描 <context:component-scan/> 和 @SpringBootApplication(scanBasePackages = "") 中的Component,並且將 @Configuration 類中的 @Bean register 到 BeanFactory 中
// 擴展例如:MyBatis MapperScannerConfigurer 和 MapperScannerRegistrar,掃描Mapper register 到 BeanFactory 中
invokeBeanFactoryPostProcessors(beanFactory);
// 註冊 BeanPostProcessor 的實現類,不同於剛剛的 BeanFactoryPostProcessor
// BeanPostProcessor 接口兩個方法 postProcessBeforeInitialization 和 postProcessAfterInitialization 會在 Bean 初始化之前和之後調用
// 這邊 Bean 還沒初始化,下面的 finishBeanFactoryInitialization 纔是真正的初始化方法
registerBeanPostProcessors(beanFactory);
// 初始化當前 ApplicationContext 的 MessageSource,解析消息的策略接口,用於支持消息的國際化和參數化
// Spring 兩個開箱即用的實現 ResourceBundleMessageSource 和 ReloadableResourceBundleMessageSource
initMessageSource();
// 初始化當前 ApplicationContext 的事件廣播器
initApplicationEventMulticaster();
// 典型模板方法
// 子類可以在實例化 bean 之前,做一些初始化工作,SpringBoot 會在這邊啓動 Web 服務
onRefresh();
// 向 initApplicationEventMulticaster() 初始化的 applicationEventMulticaster 註冊事件監聽器,就是實現 ApplicationListener 接口類
// 觀察者模式,例如實現了 ApplicationEvent,通過 ApplicationEventPublisher#publishEvent(),可以通知到各個 ApplicationListener#onApplicationEvent
registerListeners();
// 初始化所有的 singletons bean(lazy-init 的除外)
// Spring bean 初始化核心方法
finishBeanFactoryInitialization(beanFactory);
// 初始化完成(ContextRefreshedEvent)事件
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// destroy 已經創建的 singleton 避免佔用資源
destroyBeans();
// 重置'active'標誌
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
resetCommonCaches();
}
}
}
進入obtainFreshBeanFactory()方法,這個方法裏面調用了refreshBeanFactory()和getBeanFactory()方法,這兩個方法都是在子類AbstractRefreshableApplicationContext中實現的,主要看refreshBeanFactory()方法,如下:
@Override
protected final void refreshBeanFactory() throws BeansException {
//判斷當前ApplicationContext是否已經有 BeanFactory ,如果有,銷燬所有 Bean,關閉 BeanFactory
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
//創建一個可以獨立使用的容器
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
//設置 BeanFactory 的兩個配置屬性:是否允許 Bean 覆蓋、是否允許循環引用
customizeBeanFactory(beanFactory);
//解析配置文件,並將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);
}
}
loadBeanDefinitions()在子類XmlWebApplicationContext實現,代碼如下:
@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
//創建XmlBeanDefinitionReader對象,並將beanFactory綁定,XmlBeanDefinitionReader是BeanDefinitionReader的一個實現類,負責對xml的配置文件進行讀取
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// Configure the bean definition reader with this context's
// resource loading environment.
//設置環境,這裏的環境是StandardServletEnvironment,裏面主要有五項屬性:servletConfigInitParams、servletContextInitParams、jndiProperties、systemProperties、systemEnvironment
//這個StandardServletEnvironment創建比較早,在爲根上下文設置configLocationParam的時候創建的
beanDefinitionReader.setEnvironment(getEnvironment());
beanDefinitionReader.setResourceLoader(this);
//ResourceEntityResolver包含BeansDtdResolver和PluggableSchemaResolver,用以在classpath下搜尋schema和DTD文件
//PluggableSchemaResolver有一個schemaMappingsLocation屬性其值爲寫死的META-INF/spring.schemas
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
//允許子類提供reader的自定義初始化,然後繼續實際bean定義加載。這裏是空實現
initBeanDefinitionReader(beanDefinitionReader);
//加載bean的定義
loadBeanDefinitions(beanDefinitionReader);
}
繼續看loadBeanDefinitions()方法,如下:
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
//這裏獲取到的配置文件,就是在web.xml裏面配置的contextConfigLocation,一般是:classpath:spring-config.xml
String[] configLocations = getConfigLocations();
if (configLocations != null) {
for (String configLocation : configLocations) {
//解析配置文件,加載bean的定義
reader.loadBeanDefinitions(configLocation);
}
}
}
調用XmlBeanDefinitionReader父類AbstractBeanDefinitionReader的loadBeanDefinitions(String location)方法,這個方法直接調用的loadBeanDefinitions(String location, Set<Resource> actualResources)方法,這個解析過程都在這裏,如下:
public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
//獲取ResourceLoader,此處的ResourceLoader就是我們創建的XmlWebApplicationContext
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader == null) {
throw new BeanDefinitionStoreException(
"Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
}
//判斷XmlWebApplicationContext是否爲ResourcePatternResolver,通過類繼承結構可以看出,XmlWebApplicationContext是ResourcePatternResolver的子類,所以會進入第一個條件分支
if (resourceLoader instanceof ResourcePatternResolver) {
// Resource pattern matching available.
try {
//獲取resources,這裏的resources裏面放的就是spring的xml配置文件信息
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
//調用loadBeanDefinitions(Resource... resources)解析配置文件,並返回beanDefinition的數量
int loadCount = loadBeanDefinitions(resources);
//加載過程中已經被解析過的實際的Resource的填充集合
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 {
//只能通過絕對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;
}
}
說明下爲什麼要判斷當前resourceLoader是否是ResourcePatternResolver類型的,因爲ResourceLoader只是提供了對classpath前綴的支持。而ResourcePatternResolver提供了對classpath*前綴的支持。也就是說ResourceLoader提供classpath下單資源文件的載入,而ResourcePatternResolver提供多資源文件的載入。
調用loadBeanDefinitions(Resource... resources)解析配置文件,這個方法就是循環解析所有的配置文件,具體的解析,調用子類XmlBeanDefinitionReader的loadBeanDefinitions(Resource resource)這個方法,這個方法直接調用loadBeanDefinitions(EncodedResource encodedResource)這個方法,這個方法代碼如下:
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 {
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代碼中,最後真正執行操作的方法一般都是doXXXX,我們在工作中寫代碼也可以參考這種定義。
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
//將xml文件轉成標準的Document對象,採用SAX的方式解析
Document doc = doLoadDocument(inputSource, resource);
//載入並註冊
return registerBeanDefinitions(doc, resource);
}
}
繼續看org.springframework.beans.factory.xml.XmlBeanDefinitionReader#registerBeanDefinitions這個方法:
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
//這裏的documentReader使用的是DefaultBeanDefinitionDocumentReader
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
//記錄統計前的BeanDefinition數
int countBefore = getRegistry().getBeanDefinitionCount();
//加載並註冊
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
//返回本次加載的BeanDefinition數
return getRegistry().getBeanDefinitionCount() - countBefore;
}
繼續看org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#registerBeanDefinitions這個方法:
@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
logger.debug("Loading bean definitions");
Element root = doc.getDocumentElement();
//真正的註冊bean定義
doRegisterBeanDefinitions(root);
}
接着看org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#doRegisterBeanDefinitions這個方法:
protected void doRegisterBeanDefinitions(Element root) {
BeanDefinitionParserDelegate parent = this.delegate;
//創建 BeanDefinitionParserDelegate 對象,這個對象的作用是將 Document 的內容轉成 BeanDefinition 實例
this.delegate = createDelegate(getReaderContext(), root, parent);
//驗證 XML 文件的命名空間,即判斷是否含有 xmlns="http://www.springframework.org/schema/beans"
if (this.delegate.isDefaultNamespace(root)) {
//取得 beans 標籤中 profile 的屬性內容,該標籤主要用於環境的切換。例如開發過程中,一般存在測試環境和正式環境,兩者之間可能存在不同的數據源。若想要實現環境的快速切換,就可以利用 profile 來配置。
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);
//開始解析 Bean 定義
parseBeanDefinitions(root, this.delegate);
//後置處理(空實現,供子類實現,方便擴展)
postProcessXml(root);
this.delegate = parent;
}
繼續看org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader#parseBeanDefinitions這個方法:
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
//驗證 XML 文件的命名空間,即判斷是否含有 xmlns="http://www.springframework.org/schema/beans"
if (delegate.isDefaultNamespace(root)) {
//取得 <beans> 的所有子節點
NodeList nl = root.getChildNodes();
//遍歷 <beans> 的子節點
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)) {
//解析 <beans> 子節點的內容
parseDefaultElement(ele, delegate);
}
else {
//非xmlns="http://www.springframework.org/schema/beans"這個命名空間的標籤解析,例如:<context:annotation-config/>
delegate.parseCustomElement(ele);
}
}
}
}
else {
//非xmlns="http://www.springframework.org/schema/beans"這個命名空間的標籤解析,例如:<context:annotation-config/>
delegate.parseCustomElement(root);
}
}
這裏可以看到,bean的定義解析分成了兩個分支,一個是bean標籤的解析,一個是非bean標籤及自定義標籤的解析,這塊內容也很多,下篇繼續。