笔记整理自《Spring源码深度解析》(第2版),同时也参考了一些网上资源,具体参考链接在文末
DefaultListableBeanFactmy 是整个 bean 加载的核心部分,是 Spring 注册及加载bean的默认实现
图中各个类的作用:
XmlBeanFactory对 DefaultListableBeanFactory类进行了扩展,主要用于从XML文档中读取BeanDefinition,对于注册及获取bean都是使用从父类DefaultListableBeanFactory继承的方法去实现,而唯独与父类不同的个性化实现就是增加了XmlBeanDefinitionReader类型的reader属性。在XmlBeanFactory中主要使用reader属性对资源文件进行读取和注册。
实现XML配置文件读取功能
1、通过继承自AbstractBeanDefinitionReader中的方法,来使用ResourLoader将资源文件路径转换为对应的Resource文件。
2、通过DocumentLoader对Resource文件进行转换,将Resource文件转换为Document文件。
3、通过实现接口BeanDefinitionDocumentReader的DefaultBeanDefinitionDocumentReader类对Document进行解析,并使用BeanDefinitionParserDelegate对Element进行解析
分析以下功能代码实现
BeanFactory bf= new XmlBeanFactory(new ClassPathResource("be anFactoryTest.xml"));
用 ClassPathResource的构造函数来构造 Resource 资源文件的实例对象,这样后续的资源处理就可以用 Resource 提供的各种服务来操作
在Java中,将不同来源的资源抽象成URL,通过注册不同的handler来处理不同来源的资源的读取逻辑,但是URL没有默认定义相对Classpath或ServletContext等资源的handler,且也没有提供基本的方法,比如检查当前资源是否存在,检查当前资源是否可读的等方法。所以Spring使用Resource接口封装底层资源。
使用Resouce来进行XmlBeanFactory的初始化
利用XmlBeanDefinitionReader来获取beanDefinition
接下来我们分析如何获取beanDefinition
处理过程:
BeanFactory bf= new XmlBeanFactory(new ClassPathResource("be anFactoryTest.xml"));//(1)
ClassPathResource.java |
public ClassPathResource(String path) {
this(path, (ClassLoader) null);
}
public ClassPathResource(String path, @Nullable ClassLoader classLoader) {
Assert.notNull(path, "Path must not be null");
String pathToUse = StringUtils.cleanPath(path);
if (pathToUse.startsWith("/")) {
pathToUse = pathToUse.substring(1);
}
this.path = pathToUse;
this.classLoader = (classLoader != null ? classLoader : ClassUtils.getDefaultClassLoader());
}
XmlBeanDefinitionReader.java |
public XmlBeanFactory(Resource resource) throws BeansException {
this(resource, null);
}
//构造函数内部再次调用内部构造函数:
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
super(parentBeanFactory);
//加载数据在这里完成
this.reader.loadBeanDefinitions(resource);//(3)
}
XmlBeanDefinitionReader.java |
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return loadBeanDefinitions(new EncodedResource(resource));//(2)
}
EncodedResource用于对资源文件的编码进行处理,当设置了编码属性后Spring会根据相应的编码作为输入流的编码
XmlBeanDefinitionReader.java |
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
...
//从encodedResource中获取已经封装的Resource对象并再次从Resource中获取其中的inputstream
try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
//通过inputStream获取inputSource
InputSource inputSource = new InputSource(inputStream);
//存在编码要求,进行编码设置
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
//真正进入了逻辑核心部分
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());//(3)
}
...
}
XmlBeanDefinitionReader.java |
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
...
//加载对应的Document
Document doc = doLoadDocument(inputSource, resource);//(6)
//根据doc注册Bean信息
int count = registerBeanDefinitions(doc, resource); //(8)
...
}
XmlBeanDefinitionReader.java |
protected Document doLoadDocument(InputSource inputSource, Resource resource) throws Exception {
//getValidationModeForResource获取Xml的验证模式
return this.documentLoader.loadDocument(inputSource, getEntityResolver(), this.errorHandler,
getValidationModeForResource(resource), isNamespaceAware()); //(7)
}
XML中有两种验证模式:DTD、XSD,保证XML文件的正确性
Spring 中
XmlValidationModeDetector
对验证模式的确认是循环 xml 整个文件的每一行判断是否有DOCTYPE
字符串, 包含就是 DTD 验证模式, 不包含就是 XSD 模式
Spring 中 DocumentLoader
有且仅有一个实现类 DefaultDocumentLoader
DefaultDocumentLoader.java |
public Document loadDocument(InputSource inputSource, EntityResolver entityResolver,
ErrorHandler errorHandler, int validationMode, boolean namespaceAware) throws Exception {
//创建DocumentBuilderFactory
DocumentBuilderFactory factory = createDocumentBuilderFactory(validationMode, namespaceAware);
if (logger.isTraceEnabled()) {
logger.trace("Using JAXP provider [" + factory.getClass().getName() + "]");
}
//创建DocumentBuilder
DocumentBuilder builder = createDocumentBuilder(factory, entityResolver, errorHandler);
//解析inutSource,返回Document对象
return builder.parse(inputSource);
}
当我们将文件转换成Document后,就到接下来提取及注册bean了,我们回到(5),可以看到接下来要调用registerBeanDefinitions
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
...
//加载对应的Document
Document doc = doLoadDocument(inputSource, resource);
//根据doc注册Bean信息,并获取数量
int count = registerBeanDefinitions(doc, resource);
...
}
XmlBeanDefinitionReader.java |
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
// 获取 基于 Document 的Bean定义读取器
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
// 历史已有的bean定义数量
int countBefore = getRegistry().getBeanDefinitionCount();
// 注册bean定义
documentReader.registerBeanDefinitions(doc, createReaderContext(resource)); // (9)
// 注册后的数量-历史数量
return getRegistry().getBeanDefinitionCount() - countBefore;
}
BeanDefinitionDocumentReader
的作用就是进行 BeanDefinition 的注册
DefaultBeanDefinitionDocumentReader.java |
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
//Spring 进行 Document 对象解析, 并将解析结果包装成 BeanDefinition 进行注册的核心方法
doRegisterBeanDefinitions(doc.getDocumentElement());//(10)
}
前面都是XML加载解析的准备流程,接下来终于开始进行解析了
DefaultBeanDefinitionDocumentReader.java |
protected void doRegisterBeanDefinitions(Element root) {
// 父 BeanDefinitionParserDelegate 一开始为null
BeanDefinitionParserDelegate parent = this.delegate;
// 创建 BeanDefinitionParserDelegate
this.delegate = createDelegate(getReaderContext(), root, parent);
// 判断命名空间是否为默认的命名空间
// 默认命名空间: http://www.springframework.org/schema/beans
if (this.delegate.isDefaultNamespace(root)) {
// 获取 profile 属性
String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);
// 是否存在 profile
if (StringUtils.hasText(profileSpec)) {
// profile 切分后的数据
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(
profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);
if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
return;
}
}
}
// 前置处理
preProcessXml(root);
// bean definition 处理
parseBeanDefinitions(root, this.delegate);
// 后置 xml 处理
postProcessXml(root);
this.delegate = parent;
}
流程分析:
BeanDefinitionParserDelegate
对象, 值得注意的是这个设置父对象一般情况下是不存在的即 this.delegate = null
BeanDefinitionParserDelegate
对象 , BeanDefinitionParserDelegate
对象是作为解析的重要方法.profile
属性的处理补充:
- 这里提一个拓展点
profile
这个属性在 Spring 中一般用来做环境区分, 在 SpringBoot 中有一个类似的配置spring.profiles
. 在 Spring XML 模式中profile
是属于的一个属性
preProcessXml(root)
、postProcessXml(root)
这两个方法是为子类而设计的,为模版方法模式,如果需要在Bean解析前后做一些处理的话,那么只需要重写这两个类就可以了
DefaultBeanDefinitionDocumentReader.java |
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);
}
}
parseBeanDefinitions
方法主要是对一个 Element
的每个节点进行处理, 节点本身又存在多样性,
节点的多样性
根据节点多样性 Spring 提供了两个方法进行处理 parseDefaultElement
和 delegate.parseCustomElement(ele)
参考链接:
第三章 IoC 资源读取及注册