前言:
上篇文章介绍了Resource接口并引出了Spring IOC 工厂的核心内容
此片文章将讲解Spring底层 如何做到通过XmlBeanDefinitionReader 读取 XML 并封装成BeanDefinition的大概流程。
Spring读取XML封装BeanDefinition的流程
调用XmlBeanFactory构造方法。
Spring读取XML并封装成BeanDefinition是如下这行代码完成的:
BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
创建XmlBeanFactory工厂时,调用了XmlBeanFactory这个类的构造方法。
XmlBeanFactory源码:
public class XmlBeanFactory extends DefaultListableBeanFactory {
private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
//上面new XmlBeanFactory(resource对象) 会先调用此构造方法;
public XmlBeanFactory(Resource resource) throws BeansException {
//此方法内部又调用了这个类的重载方法,
this(resource, null);
}
//最终会调用此构造方法。
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
//设置父容器
super(parentBeanFactory);
//此处是最为核心的代码,this.reader 就是这个类的成员变量 XmlBeanDefinitionReader
//通过调用 XmlBeanDefinitionReader 的 loadBeanDefinitions 方法,来读取resource,也就是读取xml配置文件。
this.reader.loadBeanDefinitions(resource);
}
}
这个类中包含了两个重载的构造方法,最终会调用第二个构造。
第二个构造需要两个参数:
- resource:资源信息
- parentBeanFactory:父工厂,Spring中允许在一个工程中 有多个Spring工厂同时出现 ,情况非常少见,这种情况在Springmvc中就出现过,其中就有如下两个容器:
- DispatcherServlet 创建的子容器:childFactory
- ContextLoaderListener 创建的子容器:rootFactory
所以,spring在设计的过程中通常会兼顾父容器的情况,但是在咱们用spring开发的过程当中和阅读源码的过程中基本可以忽略父容器的情况。
我们接着来解析this.reader.loadBeanDefinitions(resource)内部的源码:
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {
//1.先调用到此方法
@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
//因为spring考虑到resource文件是需要编码的,所以此处对resource包了一层,
//但是我们现在默认的开发过程当中,不需要进行编码,所以这个编码操作没有任何作用。此处可以把这个EncodedResource,等同于resource来看。
return loadBeanDefinitions(new EncodedResource(resource));
}
//2.接着调用这个,这个才是真正的读取操作。
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集合中。不重要
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
//下面的try块的部分是最为核心的。
//try块的小括号中先获取了resource对象,接着获取了InputStream文件流信息,
//因为咱们传的是ClassPathResource,而ClassPathResource间接性的实现了Resource接口,Resource接口又继承了InputStreamSource接口,InputStreamSource接口中定义了getInputStream()方法
//所以咱们可以直接拿到InputStream文件流信息。
try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
//此处的核心是:将获取的inputStream输入流进行解析,然后封装成一个解析的工具类InputSource
InputSource inputSource = new InputSource(inputStream);
//因为encodedResource默认编码是null,所以这个if语句为false,就不会执行里面的代码。
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
/**
* 此处是解析的核心,有两个参数:
* inputSource:解析xml解析所需要的工具类
* encodedResource.getResource():需要解析的xml文件
* 通过如下解析,最终就能帮我们生成最终的BeanDefinition
*/
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}catch (IOException ex) {
throw new BeanDefinitionStoreException(
"IOException parsing XML document from " + encodedResource.getResource(), ex);
} finally {
currentResources.remove(encodedResource);
if (currentResources.isEmpty()) {
this.resourcesCurrentlyBeingLoaded.remove();
}
}
}
}
接下来我们来看 doLoadBeanDefinitions 方法里面的源码:
此处省略部分无用的代码(异常的处理,日志的输出等)
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
/*
* 解析xml,将其封装成Document对象。
* 此处需要两个参数:
* 1.inputSource:解析xml文件的工具类
* 2.resource:xml文件
* 注意:这个Document对象是针对于解析xml后所封装的对象的,document并不是spring的对象,所以spring并不需要这个对象
* 所有后期spring还会将这个对象再次转换成BeanDefinition对象
* */
Document doc = doLoadDocument(inputSource, resource);
/**
* 这个方法就是将document对象转换成BeanDefinition,这个方法也是最重要的方法
* 此处需要两个参数:
* 1.doc:根据xml所封装的document对象
* 2.resource:xml文件
* spring将document对象转换成BeanDefinition对象,返回的是注册的个数。
*/
int count = registerBeanDefinitions(doc, resource);
return count;
}
}
接着展开讲 int count = registerBeanDefinitions(doc, resource) 方法的源码:
//这里是注册BeanDefinition的核心:那么咱们继续找这块核心代码的核心。
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
//在注册新的BeanDefinition之前,先统计目前这个注册器注册了多少个BeanDefinition
int countBefore = getRegistry().getBeanDefinitionCount();
//此处是核心:这里开始根据document对象注册BeanDefinition
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
//计算本次注册的BeanDefinition数量:获取注册完之后注册器里的BeanDefinition个数,减去注册之前已经存在于注册器中的BeanDefinition个数。
return getRegistry().getBeanDefinitionCount() - countBefore;
}
接着展开讲 documentReader.registerBeanDefinitions(doc, createReaderContext(resource)) 方法的源码:
//先调用这个方法
@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
doRegisterBeanDefinitions(doc.getDocumentElement());
}
//接着调用此类的内部方法doRegisterBeanDefinitions
protected void doRegisterBeanDefinitions(Element root) {
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);
//这块的代码用处不大,原因自己看if语句中的注释
if (this.delegate.isDefaultNamespace(root)) {
//此处开始处理xml文件里的标签了,root代表xml文件的根标签<beans>
//root.getAttribute(PROFILE_ATTRIBUTE) 这个处理的是<beans profile=""></beans>中的profile属性
//profile是spring在编写xml的时候是可以定义环境的,例如:开发环境(dev),生产环境(production),到时可以根据更改此属性配置,达到切换环境的目的
//此处目前开发用的不多 不用关注,所以相应的 下面的 if 语句中的内容也不用关注。
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
//此if不用关注
if (StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
if (logger.isDebugEnabled()) {
logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec +
"] not matching: " + getReaderContext().getResource());
}
return;
}
}
}
preProcessXml(root);
//这里才是真正的注册BeanDefinition的核心方法
//root是根标签<bean>,delegate是相应的注册器
parseBeanDefinitions(root, this.delegate);
postProcessXml(root);
this.delegate = parent;
}
接着展开讲 parseBeanDefinitions(root, this.delegate) 方法的源码:
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
if (delegate.isDefaultNamespace(root)) {
//此处获取root的子节点list,然后在for循环中挨个处理
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块中的parseDefaultElement(ele, delegate)方法 和 else块中的delegate.parseCustomElement(ele)方法
if (delegate.isDefaultNamespace(ele)) {
//这个方法的目的是:解析基本标签,<bean id="" class="" parent="">
//所以这里是解析的重点
parseDefaultElement(ele, delegate);
} else {
//这个方法的目的是:解析自定义标签的
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}
基本标签:
<bean id="" class="" scope="" parent="" init-method="">
<property name value
</bean>
<bean id="" class="" scope="" parent="" init-method="">
<construt-arg>
</bean>
自定义标签,新的命名空间标签:
<context:propertyplace-holder
<context:component-scan
..
<tx:annotation-driven
<mvc:annotation-drvent
<aop:config
接着展开讲 parseDefaultElement(ele, delegate) 解析基本标签的方法源码:
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
//如果是import标签:import标签的作用是可以在此xml里引入别的xml配置文件
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
importBeanDefinitionResource(ele);
}
//如果是alias标签:alias标签的作用是定义别名的标签
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
processAliasRegistration(ele);
}
//如果是bean标签:此处是解析成BeanDefinition的核心重点
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
processBeanDefinition(ele, delegate);
}
//如果是beans标签
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
doRegisterBeanDefinitions(ele);
}
}
接着展开讲 processBeanDefinition(ele, delegate) 方法的源码:
//此方法需要两个参数:1.在之前解析的子节点element,2.解析器
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
//此处将element对象解析成BeanDefinitionHolder对象,这个BeanDefinitionHolder就相当于对BeanDefinition做了一层包装。
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
//如果bean标签中嵌套了自定义标签,那么就会经过此方法解析,对element进行再次解析,并封装成最终的bdHolder
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
//此处是注册BeanDdfinition,用BeanDefinitionReaderUtils工具类进行注册,并存储在一个Map中。
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
}
catch (BeanDefinitionStoreException ex) {
getReaderContext().error("Failed to register bean definition with name '" +
bdHolder.getBeanName() + "'", ele, ex);
}
// 此处是发布事件:整个spring容器注册完成之后,发布事件,通知spring容器,spring监听此事件,如果监听到了,进行后去处理
//但是此方法是个空方法,这是spring容器给开发者留的一个扩展点。
getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
}
}
总结:
经过上述的一系列流程,大家已经基本了解了spring是如何读取xml文件,如何解析xml文件,并到最后看到了真正解析各种标签的代码。
此文章就先到这里,在下一篇中,我会详细讲解怎样将element对象解析成BeanDefinitionHolder对象的。