spring載入bean的過程
阿新 • • 發佈:2019-02-05
首先,我在這裡舉個demo,大致演示一下怎麼獲取配置檔案中的bean:
一個applicationContext.xml配置檔案,這個不可少;
一個bean,這裡我沒用介面,直接用一個普通的類做為Spring的bean;
一個Junit測試類;
applicationContext.xml中的程式碼如下:
MyBean類中的程式碼如下:<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd"> <beans> <bean id="myBean" class="com.hushenjian.MyBean"></bean> </beans>
public class MyBean{
public void getName(String name) {
System.out.println("我的名字是:" + name);
}
}
單元測試類的程式碼如下:
執行單元測試,打印出“我的名字是:hushenjian”,測試類只有四行程式碼,但Spring到底為我們做了些什麼?下面我們就基於這樣的場景去分析bean的載入過程。public class MyTest { public static void main(String[] args) { ClassPathResource res = new ClassPathResource("applicationContext.xml"); XmlBeanFactory bf = new XmlBeanFactory(res); MyBean bean = (MyBean)beanFactory.getBean("myBean"); bean.getName("hushenjian"); } }
分析:
(1) 獲取配置檔案ClassPathResource res = new ClassPathResource("applicationContext.xml");
這一句只是讀入配置檔案,並封裝成Spring提供的Resource物件,供後面邏輯使用。在Spring內部,有超過十個以Resource結尾的類或檔案,他們處理不同型別的資原始檔,如FileSystemResource、ClassPathResource、UrlResource等,處理過程大同小異。
(2) 解析配置檔案並註冊bean
這裡面的邏輯相當複雜,涉及到眾多Factory、Reader、Loader、BeanDefinition、Perser、Registry系列介面和類,但他們做的基本事情就是將applicationContext.xml配置的Bean資訊構成BeanDefinition物件,然後放到Factory的map中(這一步就是所謂的註冊),這樣以後程式就可以直接從Factory中拿Bean資訊了。XmlBeanFactory bf = new XmlBeanFactory(res);
要跟蹤這個處理過程,大致流程如下:
a. 構造XmlBeanFactory時,會呼叫Reader物件的loadBeanDefinitions方法去載入bean定義資訊
b. 在Reader物件的doLoadBeanDefinitions驗證文件(配置檔案)模式,然後通過documentLoader物件處理資源物件,生成我們Document物件;
c. 呼叫BeanDefinitionDocumentReader物件的doRegisterBeanDefinitions去註冊bean定義資訊;
d. parseBeanDefinitions從xml文件根節點遞迴迴圈處理各個節點,對bean節點真正的處理工作委託給了BeanDefinitionParserDelegate,方法parseBeanDefinitionElement將一個bean節點轉換成一個BeanDefinitionHolder物件,這才是最終的解析過程;
e. DefaultListableBeanFactory.registerBeanDefinition利用解析好的beanDefinition物件完成最終的註冊,其實就是把beanName和beanDefinition作為鍵值對放到beanFactory物件的map;
(3) 例項化Bean
MyBean bean = (MyBean)bf.getBean("myBean");
這一步Spring同樣做了複雜的處理,但基本原理就是利用反射機制,通過bean的class屬性建立一個bean的例項,例子中是建立了一個StudentBean物件。(4) 呼叫物件的方法,沒什麼好說的。當然如果方法上做了事務、AOP之類的宣告,這一步的處理就不會那麼簡單了。
下面來分析一下bean的解析和註冊過程:
bean的解析和註冊過程就是下面這句程式碼執行的:
XmlBeanFactory bf = new XmlBeanFactory(res);
其具體程式碼為:
public class XmlBeanFactory extends DefaultListableBeanFactory {
//這裡為容器定義了一個預設使用的bean定義讀取器
private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
public XmlBeanFactory(Resource resource) throws BeansException {
this(resource, null);
}
//在初始化函式中使用讀取器來對資源進行讀取,得到bean定義資訊。
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
super(parentBeanFactory);
this.reader.loadBeanDefinitions(resource);
}
跟進去之後發現實際呼叫了XmlBeanDefinitionReader物件的loadBeanDefinitions方法。public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
//封裝資原始檔
return loadBeanDefinitions(new EncodedResource(resource));
}
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
這個方法是整個資源載入的切入點,我們先大致看看這個方法的處理流程:
a. 封裝資原始檔new EncodedResource(resource)
b. 獲取輸入流
從EncodedResource物件中獲取InputStream並構造InputSource物件
c. 然後呼叫doLoadBeanDefinitions方法完成具體的載入過程 3.2 doLoadBeanDefinitions方法
int validationMode = getValidationModeForResource(resource);
Document doc = this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler, validationMode, isNamespaceAware());
return registerBeanDefinitions(doc, resource);
這個方法的程式碼很長,如果不考慮異常處理,其實只做了三件事情: a. 獲取對XML檔案的驗證模式
b. 載入XML檔案,並得到對應的Document物件
c. 根據返回的Document物件註冊bean資訊
這裡對驗證模式不進行討論;
這裡不對Document物件的載入過程進行討論;
這裡直接進入bean的註冊方法registerBeanDefinitions
3.3 registerBeanDefinitions方法
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
// 這裡定義解析器,使用XmlBeanDefinitionParser來解析xml方式的bean定義檔案 - 現在的版本不用這個解析器了,使用的是XmlBeanDefinitionReader
if (this.parserClass != null) {
XmlBeanDefinitionParser parser =
(XmlBeanDefinitionParser) BeanUtils.instantiateClass(this.parserClass);
return parser.registerBeanDefinitions(this, doc, resource);
}
// 具體的註冊過程,首先得到XmlBeanDefinitionReader,來處理xml的bean定義檔案
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
int countBefore = getBeanFactory().getBeanDefinitionCount();
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getBeanFactory().getBeanDefinitionCount() - countBefore;
}
當把文件轉換為Document物件後,提取及註冊bean就是我們的重頭戲了。這裡並沒有看到我們想要的程式碼,而是把工作委託給了BeanDefinitionDocumentReader物件去處理
3.4 BeanDefinitionDocumentReader.doRegisterBeanDefinitions方法
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
Element root = doc.getDocumentElement();
BeanDefinitionParserDelegate delegate = createHelper(readerContext, root);
preProcessXml(root);
parseBeanDefinitions(root, delegate);
postProcessXml(root);
}
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
if (delegate.isDefaultNamespace(root.getNamespaceURI())) {
//這裡得到xml檔案的子節點,比如各個bean節點
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;
String namespaceUri = ele.getNamespaceURI();
if (delegate.isDefaultNamespace(namespaceUri)) {
//這裡是解析過程的呼叫,對預設的元素進行分析比如bean元素
parseDefaultElement(ele, delegate);
}else {
delegate.parseCustomElement(ele);
}
}
}
} else {
delegate.parseCustomElement(root);
}
}
終於走到了核心邏輯的底部doRegisterBeanDefinitions,如果說以前一直是XML載入解析的準備階段,那麼這個方法算是真正地開始進行解析了。 這個方法的程式碼我們比較熟悉,讀取Document物件,迴圈每一個bean節點,然後進行處理。
Spring有兩類Bean,一個是預設的,一個是自定義的bean,這個方法對他們分別呼叫了不同方法進行處理。 3.5 對預設標籤的處理 processBeanDefinition方法
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}
a. 首先利用委託類的parseBeanDefinitionElement方法進行元素解析,返回BeanDefinitionHolder物件bdHolder,經過這個方法,bdHolder例項已經包含我們配置檔案中對bean的所有配置資訊了,如name、class等。b. 對bdHolder進行裝飾
c. 解析完成後,要對bdHolder進行註冊,同樣,註冊過程委託給了BeanDefinitionReaderUtils去處理 3.6 delegate.parseBeanDefinitionElement(ele)
這個方法便是對預設標籤解析的全過程了,他將一個element節點轉換成BeanDefinitionsHolder物件,其中ele和bdHolder中的屬性是對應的。
a. 提取元素的id和name屬性
b. 進一步解析其他所有屬性並統一封裝到BeanDefinition型別的例項
c. 將獲取到的資訊封裝到BeanDefinitionHolder例項中。
String id = ele.getAttribute(ID_ATTRIBUTE);
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
AbstractBeanDefinition bd = createBeanDefinition(className, parent);
parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
bd.setDescription(DomUtils.getChildElementValueByTagName(ele, DESCRIPTION_ELEMENT));
parseMetaElements(ele, bd);
parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
parseConstructorArgElements(ele, bd);
parsePropertyElements(ele, bd);
parseQualifierElements(ele, bd);
return new BeanDefinitionHolder(bd, beanName, aliasesArray);
剩下的工作便是註冊解析的BeanDefinition。
3.7 BeanDefinitionReaderUtils.registerBeanDefinition這個方法並沒有做太多事情,而是直接呼叫了BeanDefinitionRegistry的註冊方法。BeanDefinitionRegistry是一個介面,有多個實現類,這裡我們使用了預設的實現DefaultListableBeanFactory。 3.8 DefaultListableBeanFactory.registerBeanDefinition
程式碼囉嗦了一大堆,實際上所謂的註冊,就是把beanName和beanDefinition物件作為鍵值對放到BeanFactory物件的beanDefinitionMap。
但Spring經常把簡單的邏輯寫的非常“囉嗦”,仔細分析程式碼,發現他完成了幾個事情:
a. 對bean物件的校驗
b. 檢查beanFactory中是否已經有同名的bean,如果有,進行相應處理
c. 把bean物件放到beanDefinitionMap中(這就是最終所謂的註冊)
d. 清除整個過程快取的物件資料
以上便是Spring對bean解析註冊的全過程,總結一下大致步驟:
1. 載入XML檔案,封裝成Resource物件
2. 呼叫Reader物件方法讀取XML檔案內容,並將相關屬性放到BeanDefinition例項
3. 將BeanDefinition物件放到BeanFactory物件