XmlBeanDefinitionReader的入口
从XmlBeanFactory寻找入口
BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource('application.xml'))先构造一个资源文件的实例对象,然后再构造BeanFactory。
Spring对其内部使用到的资源实现了自己的抽象结构,使用Resource接口封装底层资源。Resource继承自InputStreamSource,Java的InputStreamSource封装任何能返回InputStream的类,比如File,Classpath下的资源等,它只有一个方法getInputStream(),该方法返回一个InputStream对象。
Resource接口抽象了所有Spring内部使用到的底层资源,比如File,URL,ClassPath。对不同的来源的资源文件都有相应的Resource实现,有了Resource接口便可以对所有的资源文件进行统一处理。
XmlBeanfactory的初始化方法为:
// XmlBeanDefinitionReader用于真正加载资源中数据
private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
// parentBeanFactory为父类BeanFactory用于和factory合并,可以为空
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
super(parentBeanFactory);
this.reader.loadBeanDefinitions(resource);
}
this.reader.loadBeanDefinitions(resource)是加载资源的真正实现,也是资源加载的入口。
加载Bean
XmlBeanDefinitionReader加载资源的过程大概为:
- 封装资源文件,会将resource封装为EncodeResource
- 获取输入流,从resource中获取对于的InputStream并构造InputSource
- 通过构造InputSource实例和Resource实例继续调用doLoadBeanDefinitions loadBeanDefinitions实现为:
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return loadBeanDefinitions(new EncodedResource(resource));
}
EncodeResource用于对资源文件的编码进行处理,在这里只是简单的封装Resource。
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);
}
// 记录已经加载过的Resource
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
// 避免玄幻加载
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
// 获取封装的资源
try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
// 构造InputSource实例
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
// 核心逻辑
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();
}
}
}
loadBeanDefinitions主要是将Resource封装成InputSource对象,并且判断是否已经加载了该资源,如果加载了直接抛出异常,实际上还是资源的准备,doLoadBeanDefinitions()是核心处理部分。
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
// 加载XML文件
Document doc = doLoadDocument(inputSource, resource);
// 注册Bean信息
int count = registerBeanDefinitions(doc, resource);
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + count + " bean definitions from " + resource);
}
return count;
}
catch (Exception ex) {
throw ex;
}
}
doLoadBeanDefinitions()主要包含了三部分,其中1和2均在doLoadDocument()中实现:
- 获取XML的文件验证模式
- 加载XML文件,并得到对应的Document
- 根据返回的Document注册Bean信息
XML的验证方式
DTD和XSD
DTD(Document Type Definition)即文档类型定义,是一种XML约束模式语言,是XML文件的验证机制,属于XML文件组成的一部分,由非XML语言编写。DTD是一种保证XML文档格式正确的有效方法,可以通过比较XML文档和DTD文件来查看文档是否符合规范,元素和表钱是否正确。一个DTD文档包含:元素的定义规则,元素间关系的定义规则,元素可使用的属性,可使用的实体或符号规则。使用DTD验证模式需要在XML文件的头部声明,Spring种的声明为
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN"
"http://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans>
...
</beans>
XML Schema语言就是XSD(XML Schemas Definition)。XML Schema描述了XML文档的结构。可以用一个指定的XML Schema来验证某个XML文档,以检查该XML文档是否符合XML Schema的要求。文档设计者可以通过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文件位置或者URI地址(xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd)
验证模式的读取
doLoadDocument()是XmlBeanDefinitionReader类中的方法,该方法会使用this.documentLoader.loadDocument()加载XML文件,loadDocument()方法的第四个参数就是XML的验证模式,通过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的验证模式,则使用detectValidationMode()方法获取,该方法是XmlBeanDefinitionReader中的,主要是获取InputStream,然后再调用了XmlValidationModeDetector的detectValidationMode()方法获取验证模式。
public int detectValidationMode(InputStream inputStream) throws IOException {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
boolean isDtdValidated = false;
String content;
// 遍历XML文件
while ((content = reader.readLine()) != null) {
content = consumeCommentTokens(content);
// 如果读取的行是空或者注释则略过
if (this.inComment || !StringUtils.hasText(content)) {
continue;
}
// 是否包含DOCTYPE
if (hasDoctype(content)) {
isDtdValidated = true;
break;
}
// 读取到<开始符号,验证模式一定会在开始符号之前
if (hasOpeningTag(content)) {
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;
}
}
Spring用了检测验证模式的办法就是判断是否包含DOCTYPE,如果包含就是DTD,否则就是XSD。
获取Document
真正将资源转为Document是在DefaultDocumentLoader中的loadDocument()方法实现的。方法中先创建DocumentBuilderFactory,再通过DocumentBuilderFactory创建DocumentBuilder,进而解析inputSource来返回Document对象。
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver, ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
if (logger.isTraceEnabled()) {
logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");
}
DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
return builder.parse(inputSource);
}
解析并注册BeanDefinitions
把文档转为Document之后就是提取并注册Bean,该过程是在XmlBeanDefinitionReader中的registerBeanDefinitions()方法实现的
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
// 实例化后的documentReader类型为DefaultBeanDefinitionDocumentReader
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
// 记录加载Bean之前的BeanDefinition的加载个数
int countBefore = getRegistry().getBeanDefinitionCount();
// 加载并注册bean
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
// 记录本次加载的BeanDefinition的个数
return getRegistry().getBeanDefinitionCount() - countBefore;
}
进入到DefaultBeanDefinitionDocumentReader类中的registerBeanDefinitions()方法,该方法实际上调用了doRegisterBeanDefinitions()方法,并传入doc.getDocumentElement()
protected void doRegisterBeanDefinitions(Element root) {
// 处理Profile属性
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = createDelegate(getReaderContext(), root, parent);
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.isDebugEnabled()) {
logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec +
"] not matching: " + getReaderContext().getResource());
}
return;
}
}
}
// 解析前处理,子类实现
preProcessXml(root);
parseBeanDefinitions(root, this.delegate);
// 解析前处理,子类实现
postProcessXml(root);
this.delegate = parent;
}
profile属性同时在配置文件中部署两套配置来适用于生产环境和开发环境,可以方便进行切换开发,部署环境。而解析profile的过程为:首先获取beans节点是否定义了profile属性,如果定义了则到环境变量中寻找,所以会先判断environment不可能为空,因为profile是可以同时指定多个的,需要程序对其拆分,并解析每个profile是都符合环境变量中所定义的,不定义则不浪费性能解析。
解析并注册BeanDefination
Spring的XML配置中里面由两大类Bean声明,一个是默认的<bean id="xxx" calss ="xx.xx"/>,另一类是自定义的,例如<tx:annotation-driven/>。
根节点或者子节点如果是默认命名空间的话则采用parseDefaultElement()方法解析,否则使用delegate.parseCustomElement(root)方法对自定义命名空间进行解析。而判断是默认命名空间还是自定义命名空间的办法是使用node.getNamespaceURI()获取命名空间,然后并于Spring中固定的命名空间http://www.springframework.org/schema/beans进行对比,如果一致则认为是默认的声明,否则认为自定义声明。
parseBeanDefinitions()方法解析Bean时就时按照上述逻辑处理
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)) {
parseDefaultElement(ele, delegate);
}
else {
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}