前言
首先,我们来了解下容器加载的类图:
通过类图,我们可以看出BeanFactory是Spring容器的顶层接口。
在Spring中,所有的Bean都是由BeanFactory(也就是IOC容器)来进行管理的,它的作用是:实例化、定位。配置引用程序中的对象及建立这些对象间的依赖。通过类图,我们也可以看出它是一个接口,具体实现包括:DefaultListableBeanFactory、XmlBeanFactory、ApplicationContext等。
XmlBeanFactory 用于 Spring容器对 Xml 文件的加载,是容器的基础。本篇博文主要从源码角度来讲Xml文件的加载过程。
过程简要
Xml 文件加载过程如下:
- 把配置文件封装成Resource类型
- 加载整个资源
2.1 封装资源文件
2.2 获取输入流
2.3 加载bean的核心
2.3.1 获取对XML文件的验证模式
2.3.2 加载XML文件,并得到对应的Document
2.3.3 根据返回的Document注册Bean对象
源码解析
测试类准备(JDK1.8)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="myTestBean" class="MyTestBean"/>
</beans>
import lombok.Data;
@Data
public class MyTestBean {
private String testStr ="testStr";
}
public class BeanFactoryTest {
@Test
public void testSimpleLoad(){
BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("test.xml"));
MyTestBean bean= (MyTestBean) beanFactory.getBean("myTestBean");
Assert.assertEquals("testStr",bean.getTestStr());
}
}
调试过程中我们可以以
BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("test.xml"));
为入口来了解Xml文件加载过程。
XmlBeanFactory 初始化时序图
过程代码解析
这一过程主要分两步:
- 把配置文件封装成Resource类型
Resource 接口封装了所有 Spring 内部用到的底层资源: File、URL、Classpath 等
(1)定义了 3 个判断当前资源状态的方法:存在性(exists )、可读性( isReadable)、是否处于打开状态( isOpen )。
(2)提供了不同资源到 URL 、URI、File 类型的转换,以及获取 lastModified 属性、文件名(不带路径信息的文件名, getFilename())的方法 。
(3)为了便于操作, 提供了基于当前资源创建一个相对资源的方法: createRelative()。
(4)在错误处理中需要详细地打印出错的资源文件,因而提供了 getDescription()方法用来在错误处理中打印信息 。
(5)对不同来源的资源文件都有相应的 Resource 实现:文件( FileSystemResource ) 、 Classpath资源( ClassPathResource )、 URL 资源( UrlResource )、 InputStream 资源( InputStreamResource )、Byte 数组( ByteArrayResource )等
总之,Resource接口用于对所有资源文件统一处理。
- 加载整个资源
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
super(parentBeanFactory);
this.reader.loadBeanDefinitions(resource);
}
loadBeanDefinitions的时序图
这个时序图主要体现了三大流程:
1、封装资源文件:对参数Resource使用EncodedResource类进行封装
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return loadBeanDefinitions(new EncodedResource(resource));
}
2、获取输入流:从 Resource 中获取对应的 InputStream ,构造 InputSource
主要逻辑:getReader() --> 设置了编码属性则使用该编码作为输入流编码。
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());
}
}
3、用前两步的结果(构造的InputSource实例和Resource实例)来进行数据准备
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
//断言:encodeResource不能为null,若为null则抛异常
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 {
//从encodedResource中获取已经封装的Resource对象并在此从Resource中获取其中的inputStream
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
//InputSource这个类不来自于Spring,它的全路径是org.xml.sax.InputSource
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();
}
}
}
核心逻辑
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException {
try {
int validationMode = getValidationModeForResource(resource);
Document doc = this.documentLoader.LoadDocument(inputSource, getEntityResolver(),validationMode,isNamespaceAware());
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);
}
}
(1)获取对XML文件的验证模式
是否指定验证模式 --> 以此决定检测策略为指定模式还是自动模式(判断是否包含DOCTYPE,来判断文件格式时 DTD 还是XSD)
int validationMode = getValidationModeForResource(resource);
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;
}
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;
}
if (hasDoctype(content)) {
isDtdValidated = true;
break;
}
//读取到<开始符号,验证模式一定会在开始符号之前
if (hasOpeningTag(content)) {
// End of meaningful data...
break;
}
}
return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD);
}
catch (CharConversionException ex) {
return VALIDATION_AUTO;
}
finally {
reader.close();
}
}
private boolean hasOpeningTag(String content) {
if (this.inComment) {
return false;
}
int openTagIndex = content.indexOf('<');
return (openTagIndex > -1 && (content.length() > openTagIndex + 1) && Character.isLetter(content.charAt(openTagIndex + 1)));
}
(2)加载XML文件,并得到对应的Document
Document doc = this.documentLoader.LoadDocument(inputSource, getEntityResolver(),validationMode,isNamespaceAware());
XmlBeanFactoryReader 类对于文档读取委托给了 DocumentLoader 去执行, 这里的 DocumentLoader是个接口,而真正调用的是 DefaultDocumentLoader
@Override
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
// 创建DocumentBuilderFactory
DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
if (logger.isDebugEnabled()) {
logger.debug("Using JAXP provider [" + factory.getClass().getName() + "]");
}
// 创建DocumentBuilder
DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
// 解析inputSource
return builder.parse(inputSource);
}
(3)根据返回的Document注册Bean信息(提取root)
return registerBeanDefinitions(doc, resource);
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
//实例化BeanDefinitionDocumentReader
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
//记录统计前BeanDefinition的加载个数
int countBefore = getRegistry().getBeanDefinitionCount();
//加载及注册bean
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
//记录本次加载的BeanDefinition个数
return getRegistry().getBeanDefinitionCount() - countBefore;
}
createBeanDefinitionDocumentReader:
提取root,以便再次将root作为参数继续BeanDefinition的注册
@Override
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);
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)) {
if (logger.isInfoEnabled()) {
logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec + "] not matching: " + getReaderContext().getResource());
}
return;
}
}
}
//解析前处理,留给子类实现
preProcessXml(root);
//解析并注册BeanDefinition
parseBeanDefinitions(root, this.delegate);
//解析后处理,留给子类实现
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的处理
parseDefaultElement(ele, delegate);
}
else {
delegate.parseCustomElement(ele);
}
}
}
} else {
//对bean的处理
delegate.parseCustomElement(root);
}
}
对XML文件的各节点分类解析
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
if (delegate.nodeNameEquals(ele, "import")) {
this.importBeanDefinitionResource(ele);
} else if (delegate.nodeNameEquals(ele, "alias")) {
this.processAliasRegistration(ele);
} else if (delegate.nodeNameEquals(ele, "bean")) {
this.processBeanDefinition(ele, delegate);
} else if (delegate.nodeNameEquals(ele, "beans")) {
this.doRegisterBeanDefinitions(ele);
}
}