前面提到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的强大之处,他考虑到程序运行的各种情况以及一些细节,就是为了让开发者避免一些失误操作以及遇见问题能很快定位到问题。这是我一些个人浅薄的看法,有不对的地方请不吝指教。