上一篇总结了Spring利用Resource接口对配置文件的封装,接下来我们要看看Spring对封装好的资源是如何解析的,以及如何利用解析出的信息加载Bean的。
BeanFactory bf = new XmlBeanFactory(new ClassPathResoure("test.xml"));
我们继续来看这个例子,上一篇中ClassPathResource已经将配置文件封装成了Resource对象,现在Spring就可以对XmlBeanFactory进行初始化过程了,XmlBeanFactory有若干个初始化方法,我们这里使用的是针对Resource实例进行初始化的方法
package org.springframework.beans.factory.xml;
public class XmlBeanFactory extends DefaultListableBeanFactory {
public XmlBeanFactory(Resource resource) throws BeansException {
this(resource, null);
}
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
super(parentBeanFactory);
this.reader.loadBeanDefinitions(resource);
}
}
上面函数中的代码this.reader.loadBeanDefinitions(resource)才是资源加载的真正实现,也是下面要了解的重点,在此之前,在这个方法上面有一个super(parentBeanFactory)方法,我们跟踪到父类的构造函数中看一看
package org.springframework.beans.factory.support;
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory {
public AbstractAutowireCapableBeanFactory() {
super();
ignoreDependencyInterface(BeanNameAware.class);
ignoreDependencyInterface(BeanFactoryAware.class);
ignoreDependencyInterface(BeanClassLoaderAware.class);
}
}
这里有必要提及一下ignoreDependencyInterface方法。这个接口的主要功能是忽略给定接口的自动装配功能,那么这么做的目的是什么呢?
我们知道在Spring中普通的Bean是无意识的,也就是说这些Bean是意识不到Spring这个容器的存在的,这样的优点是这些Bean是和Spring解耦的,但是缺点也是显而易见的,那就是我们在这些Bean中获取到关于容器的信息。而实现了*Aware接口的Bean就可以解决这个问题,但是这样就会使程序与Spring耦合,如果离开Spring框架就会无法运行。如果我们和普通Bean一样用new方法来创建的话是无法得到Spring的信息的,我们需要把Bean交给Spring容器进行管理,让Spring把想要的信息装载到Bean中。这也就意味着实现了*Aware接口的Bean是不能和普通Bean一起自动装载的,所以需要忽略它们,至于为什么只是忽略了实现了BeanNameAware、BeanFactoryAware、BeanClassLoaderAware这三个接口的类,我们可以看一下AbstractAutowireCapableBeanFactory中的定义方法就知道了
protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) {
if (System.getSecurityManager() != null) {
AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
invokeAwareMethods(beanName, bean);
return null;
}, getAccessControlContext());
}
else {
invokeAwareMethods(beanName, bean);
}
Object wrappedBean = bean;
if (mbd == null || !mbd.isSynthetic()) {
wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
}
try {
invokeInitMethods(beanName, wrappedBean, mbd);
}
catch (Throwable ex) {
throw new BeanCreationException(
(mbd != null ? mbd.getResourceDescription() : null),
beanName, "Invocation of init method failed", ex);
}
if (mbd == null || !mbd.isSynthetic()) {
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}
return wrappedBean;
}
private void invokeAwareMethods(final String beanName, final Object bean) {
if (bean instanceof Aware) {
if (bean instanceof BeanNameAware) {
((BeanNameAware) bean).setBeanName(beanName);
}
if (bean instanceof BeanClassLoaderAware) {
ClassLoader bcl = getBeanClassLoader();
if (bcl != null) {
((BeanClassLoaderAware) bean).setBeanClassLoader(bcl);
}
}
if (bean instanceof BeanFactoryAware) {
((BeanFactoryAware) bean).setBeanFactory(AbstractAutowireCapableBeanFactory.this);
}
}
}
从AbstractAutowireCapableBeanFactory的定义方法中我们可以看到他调用了自己的invokeAwareMethods方法,这个方法的中处理了实现了BeanNameAware、BeanFactoryAware、BeanClassLoaderAware这三个接口的类,所以前面才只是忽略了这三个接口,至于其他几个Aware类型的接口为什么没有忽略,在后面看ApplicationContext的时候我们会遇到。
好了,讲完ignoreDependencyInterface方法后我们就要开始讲整个XmlBeanFactory的初始化方法中的重头戏——loadBeanDefinitions(resource),在下面我们先总结一下这个方法的流程,以便可以有目的性的看源码:
(1)封装资源文件。当进入XmlBeanDefinitionReader后首先对参数Resource使用EncodeResource类进行封装。
(2)获取输入流:从Resource中获取对应的InputStream并构造InputSource
(3)通过构造的InputSource实例和Resource实例继续调用doLoadBeanDefinitions
首先我们先要搞清楚EncodeResource类的作用是什么,通过名称我们可以大致推断这个类主要是对资源文件的编码进行处理的,其主要逻辑体现在getReader()方法中,当设置了编码属性的时候Spring会使用相应的编码作为输入流的编码
package org.springframework.core.io.support;
public class EncodedResource implements InputStreamSource {
public Reader getReader() throws IOException {
if (this.charset != null) {
return new InputStreamReader(this.resource.getInputStream(), this.charset);
}
else if (this.encoding != null) {
return new InputStreamReader(this.resource.getInputStream(), this.encoding);
}
else {
return new InputStreamReader(this.resource.getInputStream());
}
}
}
当构造好encodeResource对象后,再次传入可复用方法loadBeanDefinitions(new EncodeResource(resource))中
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return loadBeanDefinitions(new EncodedResource(resource));
}
上面的方法才是真正的数据准备阶段,也是我们需要研究的重头戏
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<>(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
}
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
try {
//从EncodeResource中获取Resource对象,并从Resource中获取InputStream
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
/将InputStream封装为InputSource对象
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
//如果encodedResource中对编码格式有要求,则在inputSource中设置编码属性。
//注意:InputSource类并不属于Spring框架,全路径是org.xml.sax.InputSource
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();
}
}
}
核心部分代码
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException {
try {
//将封装的inputSource加载成Document
Document doc = doLoadDocument(inputSource, resource);
//通过解析Document定义和注册Bean
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);
}
}
抛开各种异常处理,这个方法一共就两句话:Document doc = doLoadDocument(inputSource, resource) 和 return registerBeanDefinitions(doc, resource)所以下面我们继续跟踪这两个函数。
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
getValidationModeForResource(resource), isNamespaceAware());
}
这个方法里一共有两个重要的函数一个是documentLoader.loadDocument另一个是getValidationModeForResource,由于第二个函数的返回值是第一个函数的一个参数,所以我们先从getValidationModeForResource看起,继续跟踪代码:
protected int getValidationModeForResource(Resource resource) {
int validationModeToUse = getValidationMode();
//如果指定了验证模式则使用指定的验证模式
if (validationModeToUse != VALIDATION_AUTO) {
return validationModeToUse;
}
//如果未指定则使用自动检测
int detectedMode = detectValidationMode(resource);
if (detectedMode != VALIDATION_AUTO) {
return detectedMode;
}
return VALIDATION_XSD;
}
从上面的代码我们可以推断出这个函数的作用是获取XML的验证模式,那么XML的验证模式是什么呢,简单的说XML有两种验证模式:DTD和XSD,DTD是Document Type Definition的缩写,即文档类型定义;XSD是XML Schemas Definition的缩写即XML Schemas定义,详细的区别这里不再赘述,我们只需要关注他们两个的区别就可以了。DTD是有<!DOCTYPE ...>声明的,而XSD是有<...xsi:schemeLocation="...">声明的,我们知道了两者的区别后就很容易判断资源的验证模式了。我们继续跟踪delectValidationMode(resource)方法中调用的delectValidationMode(intputStream)方法
public int detectValidationMode(InputStream inputStream) throws IOException {
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;
}
//判断读取的行中有DOCTYPE,则一定是DTD模式
if (hasDoctype(content)) {
isDtdValidated = true;
break;
}
//DTD模式一定在'<'符号前出现,如果当前行中出现了'<'则表明一定是XSD模式
if (hasOpeningTag(content)) {
break;
}
}
return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD);
}
catch (CharConversionException ex) {
return VALIDATION_AUTO;
}
finally {
reader.close();
}
}
取得验证模式后就可以进行文件的加载了,下面是文件加载的代码
package org.springframework.beans.factory.xml;
public class DefaultDocumentLoader implements DocumentLoader {
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
if (logger.isDebugEnabled()) {
logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
}
DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
return builder.parse(inputSource);
}
}
需要注意的是这里引用的是DefaultDocumentLoader中的方法,因为XmlBeanDefinition中只是Document接口,整个方法和SAX解析XML没什么区别,都是创建文件Builder的工厂然后通过工厂创建builder再调用builder的解析方法完成解析。至此,Document的解析就完成了,下面就要开始对Bean进行加载和注册了。让我们回过头来看XmlBeanDefinition中的第二个重要的方法——registerBeanDefinitions(doc, resource)。
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
//使用DefaultBeanDefinitionDocumentReader 实例化 BeanDefinitionDocumentReader
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
//记录统计前BeanDefinition的加载个数
int countBefore = getRegistry().getBeanDefinitionCount();
//加载及注册BeanDefinition
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
//返回本次注册BeanDefinition的个数
return getRegistry().getBeanDefinitionCount() - countBefore;
}
显然这个函数依然没有涉及到核心的逻辑,我们需要继续往下跟踪,而且跟踪的函数也很明显就是egisterBeanDefinitions(doc, createReaderContext(resource))方法,下面是这个方法的代码:
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
logger.debug("Loading bean definitions");
//获取文件根节点
Element root = doc.getDocumentElement();
//将根节点传入注册方法中
doRegisterBeanDefinitions(root);
}
依然没有涉及核心逻辑,我们继续跟踪
protected void doRegisterBeanDefinitions(Element root) {
//处理递归和嵌套
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);
//验证是否符合Spring规范以及处理profile属性
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;
}
}
}
//解析Bean前的处理,默认是空函数,留给子类去实现
preProcessXml(root);
//解析Bean,整个函数的重点
parseBeanDefinitions(root, this.delegate);
//解析Bean后的处理,默认是空函数,留给子类去实现
postProcessXml(root);
//处理递归和嵌套
this.delegate = parent;
}
我们继续向下跟踪,代码如下
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)) {
//如果是默认Bean声明用默认解析方法解析Bean
parseDefaultElement(ele, delegate);
}
else {
//如果是自定义Bean声明用自定义解析方法解析Bean
delegate.parseCustomElement(ele);
}
}
}
}
else {
//如果是自定义Bean声明用自定义解析方法解析Bean
delegate.parseCustomElement(root);
}
}
上面的代码看起来逻辑还是很容易搞懂的,Spring对于默认声明和自定义声明的解析差别还是比较大的,这方面的代码到下一篇在继续总结。