spring原始碼學習之路---IOC容器初始化要義之bean定義載入(四)
阿新 • • 發佈:2018-11-10
上章說到要帶各位去看看bean定義載入的要義,其實就是loadBeanDefinitions這個方法的具體實現步驟,下面我們跟隨這個方法去看下它到底是如何載入bean定義的。
上面是我擷取的實現了loadBeanDefinitions的類級別截圖,loadBeanDefinitions方法是AbstractRefreshableApplicationContext抽象類的模板方法,而此次我們研究的FileSystemXmlApplicationContext中的loadBeanDefinitions方法是由AbstractXmlApplicationContext抽象類實現的。
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// Create a new XmlBeanDefinitionReader for the given BeanFactory.
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// Configure the bean definition reader with this context's
// resource loading environment.
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.
initBeanDefinitionReader(beanDefinitionReader);
loadBeanDefinitions(beanDefinitionReader);
}
- 第一行首先定義了一個reader,很明顯,這個就是spring為讀取XML配置檔案而定製的讀取工具,這裡AbstractXmlApplicationContext間接實現了ResourceLoader介面,所以該方法的第二行才得以成立,最後一行便是真正載入bean定義的過程。我們追蹤其根源,可以發現最終的讀取過程正是由reader完成的,程式碼如下。
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<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();
}
}
}
- 這個方法中不難發現,try塊中的程式碼才是載入bean定義的真正過程,我們一步一步的扒開bean定義的載入,spring將資源返回的輸入流包裝以後傳給了doLoadBeanDefinitions方法,我們進去看看發生了什麼。
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
int validationMode = getValidationModeForResource(resource);
Document doc = this.documentLoader.loadDocument(
inputSource, getEntityResolver(), this.errorHandler, validationMode, isNamespaceAware());
return registerBeanDefinitions(doc, resource);
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (SAXParseException ex) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(),
"Line " + ex.getLineNumber() + " in XML document from " + resource + " is invalid", ex);
}
catch (SAXException ex) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(),
"XML document from " + resource + " is invalid", ex);
}
catch (ParserConfigurationException ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"Parser configuration exception parsing XML from " + resource, ex);
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"IOException parsing XML document from " + resource, ex);
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(resource.getDescription(),
"Unexpected exception parsing XML document from " + resource, ex);
}
}
- 可以看到,spring採用documentLoader將資源轉換成了Document介面,這正是我們熟知的SAX對XML解析的重要介面之一,這下不難理解了,可以想象出spring一定是根據XSD檔案規定的XML格式,解析了XML檔案中的各個節點以及屬性。儘管如此,我們還是跟著registerBeanDefinitions方法進去看看。此處該方法不再貼出程式碼,請各位自己跟蹤進去看,這個方法裡記錄了一共註冊了多少個bean定義。最終能看出端倪的地方在DefaultBeanDefinitionDocumentReader的parseBeanDefinitions方法中,如下程式碼。
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;
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele, delegate);
}
else {
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}
- 這裡分了兩種解析路線,一個是預設的,一個是自定義的,從這裡我們可以看出,我們是可以在spring的配置檔案中自定義節點的。
- 進入parseDefaultElement方法 通過不同得標籤去解析
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
importBeanDefinitionResource(ele);
}
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
processAliasRegistration(ele);
}
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
processBeanDefinition(ele, delegate);
}
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
// recurse
doRegisterBeanDefinitions(ele);
}
}
- 通過檢視解析bean 進入processBeanDefinition方法一直找到BeanDefinitionParserDelegate 類中 parseBeanDefinitionElement方法就是通過標籤中得class類來解析document 具體可以debug進去看看
再往下走就基本上到了spring開始針對具體標籤解析的過程,各位如果有興趣可以自行跟進去看一下spring是如何對XML檔案的各個節點和屬性進行解析的,瞭解這個過程可以幫助你熟練的掌握spring中的XML配置檔案的各個節點和屬性的含義。