目录
1.2.4 doRegisterBeanDefinitions
一、 示例代码
MyTestBean.java
public class MyTestBean {
private String testStr = "testStr";
public String getTestStr() {
return testStr;
}
public void setTestStr(String testStr) {
this.testStr = testStr;
}
}
beanFactoryTest.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="myTestBean" class="bean.MyTestBean"/>
</beans>
BeanFactoryTest.java
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
import bean.MyTestBean;
/**
* @author lily
* @date 2021/8/19 16:37
*/
@SuppressWarnings("deprecation")
public class BeanFactoryTest {
@Test
public void testSimpleLoad() {
BeanFactory bf = new XmlBeanFactory(new ClassPathResource("beanFactoryTest.xml"));
MyTestBean bean = (MyTestBean) bf.getBean("myTestBean");
assertEquals("testStr", bean.getTestStr());
}
}
二、 分析
- 读取配置文件 beanFactoryTest.xml;
- 根据xml文件中的配置找到对应的类的配置,并实例化;
- 调用实例化后的实例。
1 XmlBeanFactory——容器的基础
XmlBeanFactory ≈ DefaultListableBeanFactory + XmlBeanDefinitionReader
DefaultListableBeanFactory是整个bean加载的核心部分,是spring注册及加载bean的默认实现。XmlBeanFactory继承了它,区别在与使用了自定义的XML读取器XmlBeanDefinitionReader。在XmlBeanDefinitionReader中主要包含以下几步的处理:
- 使用ResourceLoader将资源文件路径转换为对应的Resource文件;
- 通过DocumentLoader将Resource文件转换为Document文件;
- 解析Document为Element;
- 解析Element。
分析代码:
BeanFactory bf = new XmlBeanFactory(new ClassPathResource("beanFactoryTest.xml"));
1.1 配置文件封装
① Resource resource = new ClassPathResource("beanFactoryTest.xml")
Spring对其内部使用到的底层资源使用Resource接口来封装。
1.1.1 InputStreamSource
public interface InputStreamSource {
InputStream getInputStream() throws IOException;
}
该接口封装任何能返回InputStream的类,比如File、Classpath资源和Byte Array等。唯一的方法返回一个新的InputStream对象。
1.1.2 Resource
该接口抽象了所有Spring内部使用到的底层资源:File、URL、Classpath等,有了该接口便可以对所有资源文件进行统一处理。对于不同来源的资源文件有相应的实现:
- 文件(FileSystemResource)
- Classpath资源(ClassPathResource)
- URL资源(URLResource)
- InputStream资源(InputStreamResource)
- Byte数组(ByteArrayResource)
1.2 资源加载—加载Bean
可以使用如下代码加载文件:
Resource resource = new ClassPathResource("beanFactoryTest.xml");
InputStream in = resource.getInputStream();
ClassPathResource中的实现方式是通过class或者classLoader提供的底层方法进行调用,例如:
InputStream in = xxx.class.getClassLoader().getResourceAsStream("路径");
当通过Resource相关类完成了对配置文件进行封装后配置文件的读取工作就全权交给XmlBeanDefinitionReader来处理了。
② BeanFactory bf = new XmlBeanFactory(resource)
public XmlBeanFactory(Resource resource) throws BeansException {
this(resource, null);
}
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
//该行是为了忽略某些属性不会被自动初始化
super(parentBeanFactory);
//❤ 该行是资源加载的真正实现
this.reader.loadBeanDefinitions(resource);
}
1.2.1 loadBeanDefinitions
loadBeanDefinitions的整个处理过程:
- 封装资源文件。对参数Resource使用EncodedResource类进行封装,目的是考虑到Resource可能存在编码要求的情况;
- 获取输入流。通过SAX读取XML文件的方式来准备InputSource对象;
- 将Resource和InputSource传入真正的核心处理部分:doLoadBeanDefinitions(InputSource,Resource)
核心代码如下:(不是完整的代码,仅核心部分)
@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return loadBeanDefinitions(new EncodedResource(resource));
}
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
try (InputStream inputStream = encodedResource.getResource().getInputStream())
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
//❤ 核心代码
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
}
1.2.2 doLoadBeanDefinitions
doLoadBeanDefinitions的处理过程:
- 获取对XML文件的验证模式,保证了XML文件的正确性;
- 加载XML文件,并得到对应的Document;
- 根据返回的Document注册Bean信息。
核心代码如下:(不是完整的代码,仅核心部分)
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);
}...
}
① 设定了验证模式则使用设定的,否则使用自动检测的方式。自动检测时,Spring通过判断是否包含DOCTYPE来确定验证模式,包含则是DTD,不包含则是XSD;(1:VALIDATION_AUTO; 2:VALIDATION_DTD; 3:VALIDATION_XSD)
② 通过SAX解析XML文档过程。首先,创建DocumentBuilderFactory;然后,通过DocumentBuilderFactory创建DocumentBuilder;最后,解析使用DocumentBuilder解析inputSource返回Document对象;
③ registerBeanDefinitions根据返回的Document和resource注册Bean信息。
1.2.3 registerBeanDefinitions
registerBeanDefinitions的处理过程见代码中注释:
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
//① 实例化documentReader
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
//② 记录统计前BeanDefinition个数
int countBefore = getRegistry().getBeanDefinitionCount();
//③ 加载及注册bean
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
//④ 记录本次加载的BeanDefinition个数
return getRegistry().getBeanDefinitionCount() - countBefore;
}
@Override
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
//提取root
Element root = doc.getDocumentElement();
//❤ 核心逻辑
doRegisterBeanDefinitions(root);
}
可以说前面一直都是XML加载解析的准备阶段,到doRegisterBeanDefinitions才是真正地开始进行解析了。首先是对PROFILE_ATTRIBUTE属性的解析,然后就可以进行XML的读取了。
1.2.4 doRegisterBeanDefinitions
protected void doRegisterBeanDefinitions(Element root) {
if (this.delegate.isDefaultNamespace(root)) {
//处理profile属性
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)) {
return;
}
}
}
//专门处理解析
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);
//解析前处理,留给子类实现
preProcessXml(root);
//❤ 核心:XML的读取
parseBeanDefinitions(root, this.delegate);
//解析后处理,留给子类实现
postProcessXml(root);
this.delegate = parent;
}
Spring的XML配置里面有两大类Bean声明,一类是默认的,例如:
<bean id="myTestBean" class="bean.MyTestBean"/>
另一类就是自定义的,需要用户实现一些借口及配置,例如:
<tx:annotation-driven>
如果根节点或者子节点是默认命名空间(xmlns = http://www.springframework.org/schema/beans)的话就采用parseDefaultElement方法进行解析,否则使用delegate.parseCustomElement方法对自定义命名空间进行解析。
这两类解析方法见下一篇文章:
* 拓展知识 *
1 XML的验证模式
比较常用的验证模式有两种:DTD和XSD
1.1 DTD
(Document Type Definition)即文档类型定义,是一种XML约束模式语言,是XML文件的验证机制,属于XML文件组成的一部分。DTD是一种保证XML文档格式正确的有效方法,可以通过比较XML文档和DTD文件来看文档是否符合规范,元素和标签使用是否正确。
一个DTD文档包含:元素的定义规则,元素间关系的定义规则,元素可使用的属性,可使用的实体或符号规则。
要使用DTD验证模式的时候需要在XML文件的头部声明,以下是使用DTD声明方式的代码:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//Spring//DTD BEAN 2.0//EN"
"http://www.Springframework.org/dtd/Spring-beans-2.0.dtd">
-
publicId:-//Spring//DTD BEAN 2.0//EN
-
systemId:http://www.springframework.org/dtd/Spring-beans-2.0.dtd
1.2 XSD
XML Schema语言就是XSD(XML Schemas Definition)。XML Schema描述了XML文档的结构。可以用一个指定的XML Schema来验证某个XML文档,以检查该XML文档是否符合其要求。文档设计者可以通过XML Schema指定一个XML文档所允许的结构和内容,并可据此检查一个XML文档是否是有效的。XML Schema本身是一个XML文档,它符合XML语法结构。可以用通用的XML解析器解析它。
在使用XML Schema文档对XML实例文档进行检验,除了要声明①名称空间外(xmlns= http://www.springframework.org/schema/beans),还必须指定②该名称空间所对应的XML Schema文档的存储位置。
通过schemaLocation属性来指定名称空间所对应的XML Schema文档的存储位置,它包含两个部分,一部分是名称空间的URI,另一部分就是该名称空间所标识的XML Schema文件位置或URL地址(xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.
springframework.org/schema/beans/spring-beans.xsd)。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
- publicId:null
- systemId:https://www.springframework.org/schema/beans/spring-beans.xsd
2 SAX & DOM
2.1 SAX
SAX(simple API for XML)是一种XML解析的替代方法。相比于DOM,SAX是一种速度更快,更有效的方法。它逐行扫描文档,一边扫描一边解析。而且相比于DOM,SAX可以在解析文档的任意时刻停止解析,但任何事物都有其相反的一面,对于SAX来说就是操作复杂。
2.2 DOM
文档对象模型(Document Object Model,简称DOM)是W3C标准,提供了标准的解析方式,但其解析效率一直不尽如人意,这是因为DOM解析XML文档时,把所有内容一次性的装载入内存,并构建一个驻留在内存中的树状结构(节点树)。如果需要解析的XML文档过大,或者我们只对该文档中的一部分感兴趣,这样就会引起性能问题。
3 EntityResolver(参考)
3.1 何为 EntityResolver
官方解释:如果SAX应用程序实现自定义处理外部实体,则必须实现此接口,并使用setEntityResolver方法向SAX 驱动器注册一个实例。
也就是说,对于解析一个xml,sax首先会读取该xml文档上的声明,根据声明去寻找相应的dtd定义,以便对文档的进行验证,默认的寻找规则,(即:通过网络,实现上就是声明DTD的地址URI地址来下载DTD声明),并进行认证,下载的过程是一个漫长的过程,而且当网络不可用时,这里会报错,就是因为相应的dtd没找到。
3.2 EntityResolver 的作用
给项目本身提供一个如何寻找DTD 的声明方法。即:由程序来实现寻找DTD声明的过程。比如我们将DTD放在项目的某处,在实现时直接将此文档读取并返回个SAX即可,这样就避免了通过网络来寻找DTD的声明。
3.3 实现方式
- DTD:直接截取systemId最后的xx.dtd然后去当前路径下寻找;
- XSD:默认到META-INF/Spring.schemas文件中找到systemId所对应的XSD文件并加载。