前面提到bean定義加載會通過xmlbeanDefinitionReader的方法loadBeanDefinitions()實現,下面我們詳細看看這個方法做了哪些工作。
通過構造方法創建xmlbeandefinitionReader的時候,會調用父類提供的構造方法,方法如下
/**
* Create a new AbstractBeanDefinitionReader for the given bean factory.
* <p>If the passed-in bean factory does not only implement the BeanDefinitionRegistry
* interface but also the ResourceLoader interface, it will be used as default
* ResourceLoader as well. This will usually be the case for
* {@link org.springframework.context.ApplicationContext} implementations.
* <p>If given a plain BeanDefinitionRegistry, the default ResourceLoader will be a
* {@link org.springframework.core.io.support.PathMatchingResourcePatternResolver}.
* <p>If the passed-in bean factory also implements {@link EnvironmentCapable} its
* environment will be used by this reader. Otherwise, the reader will initialize and
* use a {@link StandardEnvironment}. All ApplicationContext implementations are
* EnvironmentCapable, while normal BeanFactory implementations are not.
* @param registry the BeanFactory to load bean definitions into,
* in the form of a BeanDefinitionRegistry
* @see #setResourceLoader
* @see #setEnvironment
*/
protected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) {
Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
this.registry = registry;
// Determine ResourceLoader to use.
if (this.registry instanceof ResourceLoader) {
this.resourceLoader = (ResourceLoader) this.registry;
}
else {
this.resourceLoader = new PathMatchingResourcePatternResolver();
}
// Inherit Environment if possible
if (this.registry instanceof EnvironmentCapable) {
this.environment = ((EnvironmentCapable) this.registry).getEnvironment();
}
else {
this.environment = new StandardEnvironment();
}
}
註釋翻譯:
爲給定的bean工廠創建一個AbstractBeanDefinitionReader。如果傳過來的bean工廠不僅實現了BeanDefinitionRegistry接口,而且實現了ResourceLoader接口,這個對象也將作爲一個默認的ResourceLoader使用,這種用法比較常見的是ApplicationContext。
如果給了一個普通的BeanDefinitionRegistry對象,默認的ResourceLoader是PathMatchingResourcePatternResolver,如果傳進來的beanfacory對象也實現了EnvironmentCapable接口,那麼他的環境相關信息也會被這個reader使用。否則,這個reader將初始化使用一個StandardEnvironment對象。
所有的ApplicationContext實現都是實現了EnvironmentCapable接口的,但是普通的BeanFactory沒有。
/**
* Load bean definitions from the specified XML file.
* @param resource the resource descriptor for the XML file
* @return the number of bean definitions found
* @throws BeanDefinitionStoreException in case of loading or parsing errors
*/
@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return loadBeanDefinitions(new EncodedResource(resource));
}
/**
* Load bean definitions from the specified XML file.
* @param encodedResource the resource descriptor for the XML file,
* allowing to specify an encoding to use for parsing the file
* @return the number of bean definitions found
* @throws BeanDefinitionStoreException in case of loading or parsing errors
*/
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Assert.notNull(encodedResource, "EncodedResource must not be null");
if (logger.isTraceEnabled()) {
logger.trace("Loading XML bean definitions from " + encodedResource);
}
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());
}
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();
}
}
}
XmlBeanDefinitionReader會調用loadBeanDefinitions加載資源。
/**
* Actually load bean definitions from the specified XML file.
* @param inputSource the SAX InputSource to read from
* @param resource the resource descriptor for the XML file
* @return the number of bean definitions found
* @throws BeanDefinitionStoreException in case of loading or parsing errors
* @see #doLoadDocument
* @see #registerBeanDefinitions
*/
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
Document doc = doLoadDocument(inputSource, resource);
int count = registerBeanDefinitions(doc, resource);
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + count + " bean definitions from " + resource);
}
return count;
}
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);
}
}
/**
* Actually load the specified document using the configured DocumentLoader.
* @param inputSource the SAX InputSource to read from
* @param resource the resource descriptor for the XML file
* @return the DOM Document
* @throws Exception when thrown from the DocumentLoader
* @see #setDocumentLoader
* @see DocumentLoader#loadDocument
*/
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
getValidationModeForResource(resource), isNamespaceAware());
}
private DocumentLoader documentLoader = new DefaultDocumentLoader();
查看源碼到這一步,很顯然spring會通過doLoadDocument這個方法加載我們預先定義好的xml文件。方法裏面又通過調用beandefinitionReader裏面的成員變量DefaultDocumentLoader的loadDocument方法加載xml文件
這個方法需要傳五個參數,下面逐個看看這幾個方法做了什麼操作。
- getEntityResolver()方法主要返回的是一個Entityresolver,具體可看博客
- 方法第三個參數會傳一個SimpleSaxErrorHandler,這個對象是類加載就會初始化好的,主要是與sax解析xml有關,後面單開博客說明
private ErrorHandler errorHandler = new SimpleSaxErrorHandler(logger);
- 第四個參數是調用getValidationModeForResource方法返回的校驗模式,方法源碼如下
/**
* Determine the validation mode for the specified {@link Resource}.
* If no explicit validation mode has been configured, then the validation
* mode gets {@link #detectValidationMode detected} from the given resource.
* <p>Override this method if you would like full control over the validation
* mode, even when something other than {@link #VALIDATION_AUTO} was set.
* @see #detectValidationMode
* 註釋:根據指定的resource決定xml文件的校驗模式,如果沒有明確
* 指定校驗模式,然後從方法getValidationMode獲取校驗模式,如
* 果你想完全控制這種校驗模式,重寫這個方法,即使一些
* VALIDATION_AUTO之外的東西被定義。
*/
protected int getValidationModeForResource(Resource resource) {
//獲取成員變量的validation
int validationModeToUse = getValidationMode();
if (validationModeToUse != VALIDATION_AUTO) {
return validationModeToUse;
}
//根據resource獲取validation
int detectedMode = detectValidationMode(resource);
if (detectedMode != VALIDATION_AUTO) {
return detectedMode;
}
// Hmm, we didn't get a clear indication... Let's assume XSD,
// since apparently no DTD declaration has been found up until
// detection stopped (before finding the document's root tag).
return VALIDATION_XSD;
}
/**
* 其中,上面的detectValidationMode方法最後會調用
* XmlValidationModeDetector的如下方法,這塊代碼就不在這裏
* 看了,基本也能猜到是在幹嘛,就是根據命名空間的一些內容判斷用
* 哪種校驗模式,後面有時間我們補充
*
* Detect the validation mode for the XML document in the supplied {@link InputStream}.
* Note that the supplied {@link InputStream} is closed by this method before returning.
* @param inputStream the InputStream to parse
* @throws IOException in case of I/O failure
* @see #VALIDATION_DTD
* @see #VALIDATION_XSD
*/
public int detectValidationMode(InputStream inputStream) throws IOException {
// Peek into the file to look for DOCTYPE.
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
try {
boolean isDtdValidated = false;
String content;
while ((content = reader.readLine()) != null) {
content = consumeCommentTokens(content);
if (this.inComment || !StringUtils.hasText(content)) {
continue;
}
if (hasDoctype(content)) {
isDtdValidated = true;
break;
}
if (hasOpeningTag(content)) {
// End of meaningful data...
break;
}
}
return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD);
}
catch (CharConversionException ex) {
// Choked on some character encoding...
// Leave the decision up to the caller.
return VALIDATION_AUTO;
}
finally {
reader.close();
}
}
- 第五個參數是調用isNamespaceAware方法返回的布爾值,方法如下
/**
* Return whether or not the XML parser should be XML namespace aware.
* 返回是否xml解析器應該知道xml命名空間
*/
public boolean isNamespaceAware() {
return this.namespaceAware;
}
參數確定完畢,就是調用documentloader的loadDocument方法將xml加載到document對象中。具體細節就不在這邊展開了,後面有時間會補充一下sax解析如果把xml解析爲document對象。
到此爲止,我們才做的是一些準備工作,比如xml格式校驗,xml轉換爲document對象等,下一篇博客,我們詳述bean的註冊,即 int count = registerBeanDefinitions(doc, resource);方法裏面內容,這塊就開始是核心代碼了。
最後,再囉嗦幾句,我們可以看到爲了解析xml,spring前期做了很多準備工作,比如xsd,dtd的定義以及獲取,爲了方便sax解析,將inputstream封裝爲inputSource,自定義entityresolver,自定義errorHandler等操作,好多東西我們看着沒啥用,或者有點冗餘,但是可能這就是spring的強大之處,他考慮到程序運行的各種情況以及一些細節,就是爲了讓開發者避免一些失誤操作以及遇見問題能很快定位到問題。這是我一些個人淺薄的看法,有不對的地方請不吝指教。