容器的基本实现(一)
容器的基本用法
一般来说实例化一个Bean的配置基本是这样子的的
注意:这里只贴出了几个主要的类,还有他的接口实现没有贴出来
上面这些代码其实给我们完成了以下几种功能
- 读取配置文件spring.xml
- 根据spring.xml 中配置找到对应的类的配置,并且实例化
- 调用实例化后面的实例
Spring结构组成
beans包的层级结构
核心类介绍
1. DefaultListableBeanFactory
(package org.springframework.beans.factory.support)
DefaultListableBeanFactory是整个Bean加载的核心部分,是Spring注册和加载bean的核心实现,DefaultListableBeanFactory继承AbstractAutowireCapableBeanFactory 并实现了ConfigurableListableBeanFactory以及BeanDefinitionRegistry接口,我们可以从iead导出他的类结构图,如下
下面我对每一个类做一个大致的介绍:
AliasRegistry:定义对alias的简单的增删改查等等操作
SimpleAliasRegistry: 主要使用map做为alias缓存,并且对接口AliasRegistry进行实现
SingletonBeanRegistry:定义对单例的注册以及获取
BeanFactory:定义获取Bean以及各种Bean的属性
DefaultSingletonBeanRegisty:对接口SingletonBeanRegistry各个函数的实现
HierarchicalBeanFactory:继承了BeanFactory,也就是在BeanFactory定义的功能上面增加了对parentFactory的支持
BeanDefinitionRegistry:定义了对BeanDefiniton的各种增删改操作
FactoryBeanRegistrySupport:在DefaultSingleBeanRegister基础上面增加了对FactoryBean的特殊处理
configurableBeanFactory:提供配置Factory的各种方法
ListableBeanFactory:根据各种条件获取bean的配置清单
AbstarctBeanFactory:综合FactoryBeanRegistrySupport和ConfigurableBeanFactory的功能
AutowireCapableBeanFactory:提供了创建bean,自动注入,初始化,以及应用bean的后处理器
AbstractAutowireCapableBeanFactory:综合AbstractBeanFactory并对接口AutowireCapableBeanFactory进行实现
configurableListableBeanFactory:BeanFactory配置清单,指定忽略类型以及接口等等
DefaultListableBeanFactory:综上述功能,主要是对Bean注册后的处理
2. XmlBeanDefinitionReader
(package org.springframework.beans.factory.xml)
xml配置文件的读取是Spring的重要功能,S大部分是以配置为切入点的
ResourceLoader:定义资源加载器,主要应用于根据给定的资源文件地址返回对应的Resource
BeanDefinitionReader:主要定义资源文件的读取并转换为BeanDefinition的各个功能
Environmentcapable:定义获取Environment方法
DocumnetLoader:定义从资源文件加载到转换为Document的功能
AbstractBeanDefinitionReader:对EnvironmentCapable,BeanDefinitionReader类定义功能的实现
BeanDefinitionParserDelegate:定义解析Element的方法
由此我们可以推断出spring读取xml配置文件的流程
-
(一)通过继承自AbstractBeanDefinitionReader中的方法,来使用ResourLoader将资源文件路径转为对应的Resource文件
-
(二)通过DocumentLoader对Resource文件解析转换,将Resource文件转换为Documnet文件
-
(三)通过实现接口BeanDefinitionDocumentReader的DefaultBeanDefinitionDocumentReader类对Document进行解析,并使用BeanDefinitionParseDelegate对Element进行解析
容器的基础 xmlBeanFactory( 二)
注意: 我构建的spring5的环境,这里xmlBeanFactory 在spring3.0就已经弃用所以不能使用
BeanFactory ctx = new XmlBeanFactory(new ClassPathResource(“spring.xml”));
但是我们可以用 ClassPathXmlApplication读取相关的Bean
最后我是基于Spring源码深度解析这本书来学校spring源码的,所以在这里还是研究一下xmlBeanFacctory,后面在介绍其他替代方法
首先看一下时序图
我们可以看到在测试类中首先调用了classPathResource的构造函数来构造Resource资源文件的实例对象,然后后续资源处理就可以用Resource提供的各种服务来操作,但是Resource资源是如何进行封装的
配置文件的封装
Spring的配置文件读取是通过ClassPathResource进行封装的,如new ClassPathResource(“spring.xml”)
在java中,将不同的来源的资源抽象为一个url,通过注册不同的handler来处理不同来源的资源读取逻辑,一般的handler的类型使用不同的前缀来表示,
Spring对于资源的操作(检查当前资源是否存在,当前资源是否可读。。。)提供了Resource接口
抽象了所有的Spring内部所用到的底层资源,File,URL,ClassPath等等,首先定义了3个判断当前资源状态的方法**,存在性,可读性,是否处于打开状态**,另外Resource还提供了不同资源到URL,URI类型的转换,已经获取lastModified属性,文件名,为了方便操作Resource还提供了基于当前资源创建的一个相对方法,createRelative(),getDescription()方法在错误处处理打印信息
由此图可以看出当调用super父类函数进行初始化之后,就开始调用xmlBeanDefinitionReader进行资源加载了,我们首先看他的构造函数
我们一直跟踪代码从(XmlBeanFactory →DefaultListableBeanFactory→AbstractAutowireCapableBeanFactory)到AbstractAutowireCapableBeanFactory中,发现最后调用的是这样一段代码
其中里面的忽略给的接口的自动装配功能大体来说是这样子的的
当A有属性B,那么在当Spring在获取A的Bean的时候如果他的属性还没有初始化,那么Spring会自动初始化,但是也有情况,B不能被初始化,比如B实现了BeanNameAware接口,Spring是这样子介绍的,自动装配时忽略给定的依赖接口,典型应用是通过其他方式解析Application上下文注册依赖,类似于BeanFactory通过BeanFactoryAware进行注入或者通过ApplicationContext通过ApplicationContextAware进行注入
我们看一下ignoreDependencyInterface方法,首先他分别传入了几个不同的(接口)
1 BeanNameAware
如果某个bean需要访问配置文件中本身bean的id属性,这个bean类通过实现BeanNameAware接口,在依赖关系确定之后,初始化方法之前,提供回调自身的能力,从而获得本身bean的id属性,该接口提供了 void setBeanName(String name)方法,需要指出的时该方法的name参数就是该bean的id属性。回调该setBeanName方法可以让bean获取自身的id属性
2.BeanFactoryAware
实现了BeanFactoryAware接口的bean,可以直接通过beanfactory来访问spring的容器,当该bean被容器创建之后,会有一个相应的beanfactory的实例引用。该 接口有一个方法void setBeanFactory(BeanFactory beanFactory)方法通过这个方法的参数创建它的BeanFactory实例,实现了BeanFactoryAware接口,就可以让Bean拥有访问Spring容器的能力
3.BeanClassLoaderAware
spring会把加载业务bean类时使用的类加载器暴露出来
加载Bean
既然*this.reader.loadBeanDefinitions(resource)*是资源最终加载的代码,那么我们来看一下他的时序图
从图中可以看出,整个处理过程无非就是以下几个
- 封装资源文件,当进入XmlBeanDefinitionReader后首先对参数Resource使用EncodeResource进行封装
- 获取输入流
- 通过构造的InputSource实例和Resource实例继续调用函数doLoadDefinitions
X m l B e a n D e f i n i t i o n R e a d e r \color{red}{XmlBeanDefinitionReader} XmlBeanDefinitionReader
(XmlBeanDefinitionReader.java)
@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return loadBeanDefinitions(new EncodedResource(resource));
}
👇
(XmlBeanDefinitionReader.java)
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);
}
// resourcesCurrentlyBeingLoaded是一个ThreadLocal,里面存放Resource包装类的set集合
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
if (currentResources == null) {
currentResources = new HashSet<>(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
}
//如果set中已有这个元素则返回false,进入该条件抛出异常
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
try {
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
// 注意InputSource并不来自于spring 它来自于org.xml.sax
InputSource inputSource = new InputSource(inputStream);
// 设置编码
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
// 真正加载资源文件的方法 1
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
finally {
inputStream.close();
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"IOException parsing XML document from " + encodedResource.getResource(), ex);
}
finally {
// 资源加载完毕,移除该Resource
currentResources.remove(encodedResource);
// 如果集合为空,移除
if (currentResources.isEmpty()) {
this.resourcesCurrentlyBeingLoaded.remove();
}
}
}
我们继续进入到doLoadBeanDefinitions之中
(XmlBeanDefinitionReader.java)
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
//根据不同的xml约束(dtd,xsd等),将xml文件生成对应的文档对象
//1.这个方法里面涉及xml的解析
Document doc = doLoadDocument(inputSource, resource);
//注册BeanDefinitions
int count = registerBeanDefinitions(doc, resource);
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + count + " bean definitions from " + resource);
}
return count;
}
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);
}
}
doLoadBeanDefinitions分为三个部分即
- 获取xml的验证方式
- 加载xml文件,并转为对应发Document
- 根据Document返回对应的Bean的注册信息
下面我们一步一步的来
获取XML的验证方式
首先我们应该xml文件的验证保证了xml的准确性,而xml文件验证比较常用的模式有两种DTD和XSD
DTD和XSD的区别
DTD(Documnet Type Definition)属于文档类型定义,是一种XML约束模式语言,是XML文件的一种验证机制,属于XML文件的组成部分
DTD文档包含:元素的定义,元素间关系的定义规则,元素可使用属性,可使用的实体或者是符合规则
使用DTD需要在头部声明,如果Spring中使用DTD, 不过
现
在
基
本
已
被
X
S
D
文
档
取
代
\color{red}{现在基本已被XSD文档取代}
现在基本已被XSD文档取代
XSD
XML Schema语言就是XSD(XML Schemas Definition),它描述了XML文档的结构,可以用一个指定的XML Schema来验证某一个XML文档,用来检查这个XML文档是否符合规范
在使用XML Schema文档对XML实例进行检验时,除了要声明命名空间外,还必须指定这个命名空间对应的XML Schema文档的存储位置
通schemaLoaction属性指定命名空间对应的xml Schema文件包含两个部分:
(一)名称空间的URL
(二)该命名空间所包含的XML Schema的文件位置或URL地址
验证模式的读取
上面我们简单的介绍了一下xsd和dtd,下面我们看一下Spring的验证模式是怎么读取的
进入doLoadDocument
继续点击getValidationModeForResource
这里的方法调用顺序为(都在XmlBeanDefinitionReader这个类里面调用)
l
o
a
d
B
e
a
n
D
e
f
i
n
i
t
i
o
n
s
→
d
o
L
o
a
d
B
e
a
n
D
e
f
i
n
i
t
i
o
n
s
→
d
o
L
o
a
d
D
o
c
u
m
e
n
t
→
g
e
t
V
a
l
i
d
a
t
i
o
n
M
o
d
e
F
o
r
R
e
s
o
u
r
c
e
\color{red}{loadBeanDefinitions→doLoadBeanDefinitions→doLoadDocument→getValidationModeForResource}
loadBeanDefinitions→doLoadBeanDefinitions→doLoadDocument→getValidationModeForResource
(XmlBeanDefinitionReader.java)
/*
* 通过给定Resource给出验证模式。如果没有明确配置验证模式,那么调用detectValidationMode方法去 检测,可以通过xmlBeanDefintionReader.setValidationMode来进行设定
*/
protected int getValidationModeForResource(Resource resource) {
// 默认自动验证 1
int validationModeToUse = getValidationMode();
// 如果手动指定验证模式则使用指定的验证模式
if (validationModeToUse != VALIDATION_AUTO) {
return validationModeToUse;
}
//如果有给出具体验证方式,则使用自动检测返回结果
int detectedMode = detectValidationMode(resource);
if (detectedMode != VALIDATION_AUTO) {
return detectedMode;
}
// 如果实在不能判断验证模式是那种就使用XSD方式,
// 因为检测完后还是没有发现DTD模式的声明(在查找document的根标签之前)。
// 值为3
return VALIDATION_XSD;
}
因为自动检测模式是在函数detectValidationMode方法里面实现的,我们进入detectValidationMode
`
(XmlBeanDefinitionReader.java)
//检测执行xml文件时该用哪种验证方式,这个xml由Resource对象提供
// 如果这个文件有DOCTYPE声明,那么就用DTD验证,否则就假定使用XSD。
// 如果你想要自定义自动验证模式的解决方式,你可以覆盖这个方法
protected int detectValidationMode(Resource resource) {
// 默认false
if (resource.isOpen()) {
throw new BeanDefinitionStoreException(
"Passed-in Resource [" + resource + "] contains an open stream: " +
"cannot determine validation mode automatically. Either pass in a Resource " +
"that is able to create fresh streams, or explicitly specify the validationMode " +
"on your XmlBeanDefinitionReader instance.");
}
InputStream inputStream;
try {
inputStream = resource.getInputStream();
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"Unable to determine validation mode for [" + resource + "]: cannot open InputStream. " +
"Did you attempt to load directly from a SAX InputSource without specifying the " +
"validationMode on your XmlBeanDefinitionReader instance?", ex);
}
try {
return this.validationModeDetector.detectValidationMode(inputStream);
}
catch (IOException ex) {
throw new BeanDefinitionStoreException("Unable to determine validation mode for [" +
resource + "]: an error occurred whilst reading from the InputStream.", ex);
}
}
由上面的代码可以看出来xmlBeanDefintionReader的detectValidationMode函数又调用了XmlValidationModeDetector的detectValidationMode
这里的方法调用顺序为(ps:[…]这里理解为上面的加上上面的调用是他的整体调用流程)
[
.
.
.
]
→
d
e
t
e
c
t
V
a
l
i
d
a
t
i
o
n
M
o
d
e
(
类
:
X
m
l
B
e
a
n
D
e
f
i
n
i
t
i
o
n
R
e
a
d
e
r
)
→
d
e
t
e
c
t
V
a
l
i
d
a
t
i
o
n
M
o
d
e
(
类
:
X
m
l
V
a
l
i
d
a
t
i
o
n
M
o
d
e
D
e
t
e
c
t
o
r
)
\color{red}{[...]→detectValidationMode(类:XmlBeanDefinitionReader)→detectValidationMode(类:XmlValidationModeDetector)}
[...]→detectValidationMode(类:XmlBeanDefinitionReader)→detectValidationMode(类:XmlValidationModeDetector)
( XmlValidationModeDetector.java)
public int detectValidationMode(InputStream inputStream) throws IOException {
// 查看文件以寻找DOCTYPE
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;
}
//有DOCTYPE声明,就跳出去
if (hasDoctype(content)) {
isDtdValidated = true;
break;
}
//注释不能进去。开头是"<",后面第一个字符是字母,就进入。
//比如'<beans xmlns="http://www.springframework.org/schema/beans"'
//进去后跳出循环
if (hasOpeningTag(content)) {
// End of meaningful data...
break;
}
}
//当遍历到名称空间了也就是"<beans xmlns=...>"还没有DOCTYPE声明,
//那么就判定他为XSD验证
return (isDtdValidated ? VALIDATION_DTD : VALIDATION_XSD);
}
catch (CharConversionException ex) {
// Choked on some character encoding...
// Leave the decision up to the caller.
return VALIDATION_AUTO;
}
finally {
reader.close();
}
}
我们可以看到consumeCommentTokens
@Nullable
private String consumeCommentTokens(String line) {
// 首先如果没有带<!-- 或 -->的直接返回内容代表没有再注释里面
int indexOfStartComment = line.indexOf(START_COMMENT);
if (indexOfStartComment == -1 && !line.contains(END_COMMENT)) {
return line;
}
String result = "";
String currLine = line;
if (indexOfStartComment >= 0) {
result = line.substring(0, indexOfStartComment);
currLine = line.substring(indexOfStartComment);
}
while ((currLine = consume(currLine)) != null) {
//没有在注释中或者不是由注释开头的返回内容,inComment的这个标识位表示当前是否在注释中
if (!this.inComment && !currLine.trim().startsWith(START_COMMENT)) {
return result + currLine;
}
}
return null;
}
至此xmL的验证已经完成,下一章我继续总结Spring是如何获取Domcument的