接着上一篇文章,本篇來接着談談 Spring 容器的創建及 bean 定義的解析。Debug 使用的代碼和上一篇文章提供的保持一致
本篇文章由於代碼調用流程較長,所以先給大家呈現一張調用流程圖,方便大家更容易理解調用層次,廢話不多說了,先上圖一睹爲快。
創建BeanFactory及註冊BeanDefinition流程
直接來看下
obtainFreshBeanFactory
源碼實現
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
// Step1:創建BeanFactory並註冊BeanDefinition定義
// 通過這個方法可以看出,所有實現是在這個方法裏面完成的
refreshBeanFactory();
// Step2:獲取創建好的BeanFactory對象,並返回
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (logger.isDebugEnabled()) {
logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
}
return beanFactory;
}
refreshBeanFactory
實現其實調用了其子類AbstractRefreshableApplicationContext.refreshBeanFactory()
方法。
protected final void refreshBeanFactory() throws BeansException {
// Step1:判斷BeanFactory是否已經創建好,一般情況下第一次初始化Spring容器的時候if是不成立的,因爲在此之前沒有地方可以創建出BeanFactory對象
if (hasBeanFactory()) {
// 如果已經創建好,先將創建好的bean實例,相關緩存,BeanDefinition定義全部清空
// Spring源碼中大量運用了緩存,來提高Spring容器初始化的速度
destroyBeans();
// 然後在將BeanFactory置爲null
closeBeanFactory();
}
try {
// Step2:創建一個DefaultListableBeanFactory對象,該對象用於存儲Bean實例、相關緩存、BeanDefinition定義等等
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
// Step3:設置是否允許BeanDefinition是否可以被覆蓋
// 設置循環引用
customizeBeanFactory(beanFactory);
// Step4:加載BeanDefinition定義,並將其註冊到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);
}
}
loadBeanDefinitions()
實現由AbstractRefreshableApplicationContext
子類AbstractXmlApplicationContext.loadBeanDefinitions()
方法實現的。
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// Create a new XmlBeanDefinitionReader for the given BeanFactory.
// Step1:創建一個XmlBeanDefinitionReader對象,用於讀取xml文檔
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// Configure the bean definition reader with this context's
// resource loading environment.
// Step2:設置環境變量
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.
// Step3:初始化BeanDefinitionReader,設置是否啓用xml文檔校驗,默認情況下是需要校驗的
// 說白了就是是否要校驗xml文檔的合法性,避免錯誤解析
initBeanDefinitionReader(beanDefinitionReader);
// Step4:加載beanDefinitions
loadBeanDefinitions(beanDefinitionReader);
}
初始化
XmlBeanDefinitionReader
對象完成後,開始讀取配置文件資源。
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
// Step1:首先讀取ClassPathXmlApplicationContext裏面存放點的Resource資源
Resource[] configResources = getConfigResources();
if (configResources != null) {
// Step1.1:如果Resource資源不爲空,則開始加載Resource資源
reader.loadBeanDefinitions(configResources);
}
// Step2:讀取AbstractRefreshableConfigApplicationContext指定配置文件資源
String[] configLocations = getConfigLocations();
if (configLocations != null) {
// Step2.1:如果Location資源不爲空,則開始加載Location資源
reader.loadBeanDefinitions(configLocations);
}
}
獲取到配置文件路徑後,開始遍歷解析每個配置文件,不管是第一種資源還是第二種資源,最後都統一調用了
XmlBeanDefinitionReader
類的父類AbstractBeanDefinitionReader的loadBeanDefinitions()
方法。
public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
// Step1:獲取資源讀取器
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader == null) {
throw new BeanDefinitionStoreException(
"Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
}
// Step2:如果當前的資源讀取器實現了ResourcePatternResolver接口
if (resourceLoader instanceof ResourcePatternResolver) {
// Resource pattern matching available.
try {
// Step2.1:獲取resource資源
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
// Step2.2:加載所有指定的resource資源裏面定義的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 + "]");
}
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.
// Step3:如果沒有實現ResourcePatternResolver接口,則根據當前的resourceLoader獲取Resource資源
Resource resource = resourceLoader.getResource(location);
// Step3.1:加載指定的Resource資源定義的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;
}
}
上面這段代碼主要作用是使用不同的
BeanDefinitionReader
讀取器讀取不同類型的配置文件。拿我們提供的測試例子,最後其實調用了XmlBeanDefinitioneader.loadBeanDefinitions()
方法。在這個方法裏面我們看到一個比較特別的方法,叫做doLoadBeanDefinitions()
,其實真正加載BeanDefinition
是在這個方法裏面實現的。
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());
}
// Step1:獲取當前的Resource對象,剛開始肯定是沒有的
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
if (currentResources == null) {
// Step1.1:將當前Resource設置進去
currentResources = new HashSet<>(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
}
// Step2:只有在第一次添加的時候纔會返回true,也就意味着如果當前resource已經被設置過了,則出現了重複加載,直接拋出異常
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
try {
// Step3:根據resource對象獲取對應的InputStream
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
// Step4:拿到inputSource對象,開始正式讀取配置文件,並創建配置文件中定義的BeanDefinition
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
finally {
// Setp5:關閉輸入流
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();
}
}
}
拿到了
InputSource
之後,來看看配置文件到底是如何被解析的。
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
// Step1:加載Document對象,其實就是把xml文件解析成一個文檔對象返回
Document doc = doLoadDocument(inputSource, resource);
// Step2:對Document對象遍歷,讀取不同的標籤名,創建對應的BeanDefinition,並將創建出來的BeanDefinition註冊到BeanFactory中去
return registerBeanDefinitions(doc, resource);
}
// 此處爲了方便閱讀,將各種異常代碼用.....代替
......
catch (Throwable ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"Unexpected exception parsing XML document from " + resource, ex);
}
}
先來看看
Document
對象是怎麼創建出來的。
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
getValidationModeForResource(resource), isNamespaceAware());
}
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
// Step1:創建Document工廠,這裏面其實就是設置配置文件的文件頭
// 比如applicationContext.xml文件裏面最開始,beans標籤裏面指定的 .xsd。瞭解即可
DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
if (logger.isDebugEnabled()) {
logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
}
// Step2:利用Document工廠,創建對應的DocumentBuilder。
DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
// Step3:利用builder去解析輸入流,從而構建出Document對象
return builder.parse(inputSource);
}
// 這個方法就是文檔的一些解析處理,瞭解一下
public Document parse(InputSource is) throws SAXException, IOException {
if (is == null) {
throw new IllegalArgumentException(
DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN,
"jaxp-null-input-source", null));
}
if (fSchemaValidator != null) {
if (fSchemaValidationManager != null) {
fSchemaValidationManager.reset();
fUnparsedEntityHandler.reset();
}
resetSchemaValidator();
}
domParser.parse(is);
Document doc = domParser.getDocument();
domParser.dropDocumentReferences();
return doc;
}
到現在,已經將 xml 文件解析成
Document
對象了,接下來看看根據Document
對象如何創建BeanDefinition
對象並註冊到BeanFactory
裏面。
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
int countBefore = getRegistry().getBeanDefinitionCount();
// 這裏我們只關注如何把Document對象註冊到BeanFactory裏面
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}
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.
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);
// Step1:以beans開始,是否可以取到 “profile”標籤,我們沒有在xml文件裏面指定,所以這個地方可以忽略
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;
}
}
}
// Step2:解析前工作,默認沒有實現,留出來擴展
preProcessXml(root);
// Step3:開始解析Document
parseBeanDefinitions(root, this.delegate);
// Step4:解析後工作,默認沒有實現,留出來擴展
postProcessXml(root);
this.delegate = parent;
}
重點來了,我們在 xml 文件裏面配置的
Bean
標籤就是在這個裏面被轉化成一個個BeanDefinition
對象的。
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
// Step1:如果是默認的命名空間,使用默認的解析邏輯
if (delegate.isDefaultNamespace(root)) {
// Step1.1:獲取所有子節點
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;
// Step1.2:如果子節點是 “import”、“alias”、“bean”、“beans”等等的
// 則走默認的標籤解析處理邏輯
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele, delegate);
}
else {
// Step1.3:如果不是上面指定的這些標籤的話,則走特殊的標籤解析邏輯
delegate.parseCustomElement(ele);
}
}
}
}
// Step2:不是默認的命名空間,則走自己的解析邏輯
else {
delegate.parseCustomElement(root);
}
}
總結
從本篇文章不難看出,obtainFreshBeanFactory 就是創建了一個 BeanFactory 對象,然後把 xml 配置文件解析成 Document 對象,然後對 Document 對象遍歷,創建出相應的 BeanDefinition 定義,並把 BeanDefinition 對象註冊到 BeanFactory 裏面。
到這裏我們拿到了 xml 配置的 bean 標籤。限於文章篇幅過長,我在下一篇文章中來繼續解釋,bean 標籤是如何被解析的,比如 id,class,scope 等等屬性。
歡迎關注我,共同學習