前言
剖析SpringIOC容器初始化源码的文章有很多,但大多都是直接上代码注释或直接根据UML图逐行解读源码。我认为即使如此,阅读源码的工作依然是否艰巨。因为Spring作为一个风靡全球的框架,其代码里面存在大量的bug修复痕迹,很多地方写的代码还可能是为了某种特定场景专门定制,这巨大的信息差会使得初学者学习曲线过于陡峭。于此同时我觉得生啃源码的行为也让我们忘记了通过阅读Spring源代码来提升编码能力的初衷:Spring框架本身的一个设计理念是极好的,在代码里面也大量使用了各种设计模式,同时将面向对象使用的淋漓尽致。所以下面我会从对象的职责交互、使用到的设计模式两个方面阐释SpringIOC容器的初始化过程。
写在前面
本文将从已废除的XmlBeanFactory进行源码剖析,原因是它既简单又能够体现SpringIOC容器的具体初始化过程。 文章是以spring-framework:5.3.21为基础讲解,希望读者能够同步源码,跟着作者的思路一起阅读这份代码。 文章主体脉络如下:
- 回顾Spring使用XML文件依赖注入一个bean的例子
- 通过对象委托流程图描述这个例子内使用对象的一个交互情况
- 源码解读
- XmlBeanFactory如何通过XML文件初始化
- 如何通过XmlBeanFactory获取到一个单例bean
回顾Spring的Xml基础使用
估计现在大部分的人都开始使用SpringBoot进行业务开发,SpringBoot是个好框架。但是使用多了,容易忘了老本行。下面我们就一起回顾一下SPring的Xml基础使用吧
Demo
文章以Spring早已废除掉的XmlBeanFactory为例子讲述IOC的初始化过程,因为既简单又能体现IOC最基础的容器初始化过程。
先写一段入口程序
public class SpringApplication {
public static void main(String[] args) {
//读取beanFactory.xml并返回BeanFactory
BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("beanFactory.xml"));
//通过beanFactory获取到helloWorld的实例
HelloWorld helloWorld = (HelloWorld) beanFactory.getBean("helloWorld");
//bean实例是否正常注入了name
System.out.println(helloWorld.getName());
}
}
再写一个xml bean描述文件
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="helloWorld" class="com.lyj.spring.demo.HelloWorld">
<property name="name" value="你好,世界"/>
</bean>
</beans>
通过上面的代码,java程序启动后,XmlBeanFactory
会将beanFactory.xml进行解析,转换,实例化对象,注入属性值以供main方法使用(依赖注入)
对象委托流程图
spring框架大量使用了委托者模式、工厂模式以及模板模式。 而在BeanFactory的初始化过程中,委托者模式用的最多,这也是初始化代码难读的关键所在。下面我列了一份对象委托流程图
主要涉及四个类对象
- XmlBeanFactory
- XmlBeanDefinitionReader
- DefaultBeanDefintionDocumnentReader
- BeanDefinitionParseDeletegate
通过这四个类,主要的任务就是解析XML文件内容成为可供解读的BeanDefinition
。最终真的在干解析任务的是BeanDefinitionParseDeletegate
,其他对象都做一些异常检查和流程控制等工作。BeanDefinitionParseDeletegate
解析完的结果将会把结果注入到XmlBeanFactory的成员变量中
源码解读
下面我们来阅读第一行代码
BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("beanFactory.xml"));
从这行代码中,我们可以看到beanFactory.xml这个文件名通过ClassPathResource
的构造方法传入进去,并把ClassPathResource
的实例通过XmlBeanFactory
的构造方法传入到XmlBeanFactory
中。很典型的委托者模式,XmlBeanFactory
把读取beanFactory.xml文件流的工作交给了ClassPathResource
,并把持有文件流的ClassPathResource
引用交给XmlBeanFactory
进行后续的工作。
接下来解读一下XmlBeanFactory
的构造函数
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
super(parentBeanFactory);
this.reader.loadBeanDefinitions(resource);
}
- 第一句为父类工厂的初始化工厂(A.xml通过import标签将B.xml引入,那么A就是B的父工厂),很明显,我们的xml没有父工厂,因和主流程无关,我们暂且跳过。
- 第二句就是咱们文字的重点:
this.reader.loadBeanDefinitions(resource);
这也是典型的委托者模式,XmlBeanFactory
将Xml文件解析任务委托给了XmlBeanDefinitionReader
。
那么这里还有个问题,既然有委托的工作是希望XmlBeanDefinitionReader解析并返回可供XmlBeanFactory使用的元数据,那么如何将结果返回给XmlBeanFactory呢?
我们可以看下this.reader.loadBeanDefinitions(resource)
这个reader的初始化语句
private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
通过这个初始化语句,我们可以看到XmlBeanFactory
将自身引用交给了XmlBeanDefinitionReader
。这里的目的很明显,就是在loadBeanDefinitions
方法里面将解析好的结果通过this注册到XmlBeanFactory中。
下面我们继续剖析this.reader.loadBeanDefinitions(resource)
这句话吧!
按照上面的对象委托图XmlBeanDefinitionReader
也并没有真正解析,只是做了一些资源的封装和转换,将任务继续委托给DefaultBeanDefinitionDocumentReader
class XmlBeanDefinitionReader{
@Override
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
//将ClassPathResource包装为EncodedResource
//EncodedResource这个类原本是用来解决文件的字符集问题的,
//但是在XmlBeanDefinitionReader中并没有用到这一功能,仅用了ClassPathResource也可提供的功能。
//那么为什么引而不用?这其实是spring留给开发者的一个拓展点,当xml文件具有字符集转换需求时
//直接重写loadBeanDefinitions(Resource resource)方法,并在实例化EncodedResource时
//假如需要转换的字符集编码即可
return loadBeanDefinitions(new EncodedResource(resource));
}
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
//省略了检查encodedResource和多线程加载xml文件的代码...
//获取输入流
try (InputStream inputStream = encodedResource.getResource().getInputStream()) {
//初始化inputSource
//至于为什么要转,是因为spring要调用XAS库对xml文件节点进行解析,而XAS库仅支持inputSource输入流
//但这并不影响咱们的理解,最终读的还是一个流
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
//do开头的才是干实事的
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
//省略了一些异常处理代码...
}
}
经过上面的一大串代码,我们才完成了ClassPathResource转换成EncodedResource并获取到了InputSource的工作,真正解析的动作还得看doLoadBeanDefinitions。
class XmlBeanDefinitionReader{
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
try {
//将流转换成Document文档,方便后面的解析工作
Document doc = doLoadDocument(inputSource, resource);
int count = registerBeanDefinitions(doc, resource);
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + count + " bean definitions from " + resource);
}
return count;
}
//省略一大波异常语句
}
}
doLoadDocument这个方法主要做了xml文件校验的工作(DTD,XSD),不在本文的讨论范围内,后面会开一个课题单独讲解。下面我们将精力放到registerBeanDefinitions这个方法中,在这个方法中,XmlBeanDefinitionReader继续将解析任务委托给了DefaultBeanDefinitionDocumentReader
class XmlBeanDefinitionReader{
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
//DefaultBeanDefinitionDocumentReader
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
int countBefore = getRegistry().getBeanDefinitionCount();
//委托者模式,包装上下文并将解析和注册工作又交给了DefaultBeanDefinitionDocumentReader
//上下文中包含XmlBeanFactory的引用,能将解析结果直接注册到XmlBeanFactory中
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}
public XmlReaderContext createReaderContext(Resource resource) {
//通过构造函数,这个上下文环境包含着ClassPathResource、问题报告、事件监听、sourceExtractor、XmlBeanDefinitionReader、xml命名空间等引用
//其中XmlBeanDefinitionReader又包含着XmlBeanFactory的引用
return new XmlReaderContext(resource, this.problemReporter, this.eventListener,
this.sourceExtractor, this, getNamespaceHandlerResolver());
}
}
DefaultBeanDefinitionDocumentReader
接到委托任务后,将任务分开了两个:节点遍历控制和节点转换
节点遍历在 parseBeanDefinitions(root, this.delegate);
方法中
而节点转换则又继续委托给了BeanDefinitionParserDelegate
class DefaultBeanDefinitionDocumentReader{
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
//将上下文保存到成员变量中,方便后面操作使用
this.readerContext = readerContext;
//读取节点,每个文件支持beans标签的嵌套,但不支持两个并列的beans标签
//从这句话就能看出,只读了一个根节点
doRegisterBeanDefinitions(doc.getDocumentElement());
}
protected void doRegisterBeanDefinitions(Element root) {
//一般来说,这个parent都为空,只有在beans标签内再内嵌beans标签的操作时才会有值
BeanDefinitionParserDelegate parent = this.delegate;
//创建更深的委托,去执行标签的解析;
//如果parent不为空,那么证明这个调用来自于嵌套beans的解析,需要继承自身没有而 父类beans具有的默认属性
this.delegate = createDelegate(getReaderContext(), root, parent);
//省略一大波检查profile的代码...
//模板设计模式,给开发者一次前置修改的机会
preProcessXml(root);
//真正处理解析的方法
parseBeanDefinitions(root, this.delegate);
//模板设计模式,给开发者一次后置置修改的机会
postProcessXml(root);
this.delegate = parent;
}
}
doRegisterBeanDefinitions(Element root)这个方法有两处调用,第一处是这里,第二处则来自于这个方法的递归处理(处理beans标签的地方),也就是说在createDelegate方法中,parent有值则一定来自于beans标签嵌套情况,而为空,就是没有beans嵌套情况。
下面将从没有beas嵌套情况继续说,当执行到parseBeanDefinitions方法的时候,我们可以看到根节点和处理解析的委托者delegate都传进了方法之中
class DefaultBeanDefinitionDocumentReader{
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
//标准的spring标签
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)) {
//将子节点交给delegate进行解析
parseDefaultElement(ele, delegate);
}
else {
//自定义的spring标签,如第三方实现的事务管理器标签。这个分支不在本文的讨论范围内
delegate.parseCustomElement(ele);
}
}
}
}
//自定义的spring标签,如第三方实现的事务管理器标签。这个分支不在本文的讨论范围内
else {
delegate.parseCustomElement(root);
}
}
/**
* 解析import标签、alias标签、bean标签和beans嵌套标签
* 下面重点举例讲解bean标签的解析
**/
private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
//解析import标签
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
importBeanDefinitionResource(ele);
}
//解析别名标签
else if (delegate.nodeNameEquals(ele, ALIAS_ELEMENT)) {
processAliasRegistration(ele);
}
//解析bean标签,重点举例解析
else if (delegate.nodeNameEquals(ele, BEAN_ELEMENT)) {
processBeanDefinition(ele, delegate);
}
//解析嵌套的beans,就是我上文说到的递归调用的地方
else if (delegate.nodeNameEquals(ele, NESTED_BEANS_ELEMENT)) {
// recurse
doRegisterBeanDefinitions(ele);
}
}
/**
*解析bean标签
**/
protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
//将标签的属性转换为BeanDefinitionHolder
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
//给用户一次修饰BeanDefinitionHolder的机会
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
// 将BeanDefinitionHolder注册到XMLBeanFactory中
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
}
//省略异常处理和事件触发钩子事件代码...
}
}
}
代码讲了这么多,这里才真正开始进行单个标签的解析与注册工作,接下来继续逐个代码深挖
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele); 这段代码调用了delegate的parseBeanDefinitionElement方法,并返回了spring容器里面可用的BeanDefinitionHolder实例对象,我们看看它delegate是如何解析的
class BeanDefinitionParserDelegate{
/**
*转换工作,真正干活的地方
**/
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
//获取id属性值
String id = ele.getAttribute(ID_ATTRIBUTE);
//获取name属性值
String nameAttr = ele.getAttribute(NAME_ATTRIBUTE);
//新建别名集合
List<String> aliases = new ArrayList<>();
//添加0或1或多个别名,供xmlBeanFactory使用
if (StringUtils.hasLength(nameAttr)) {
String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, MULTI_VALUE_ATTRIBUTE_DELIMITERS);
aliases.addAll(Arrays.asList(nameArr));
}
//id就是bean的实例名
String beanName = id;
//如果没有id,就会用别名中的一个个代替
if (!StringUtils.hasText(beanName) && !aliases.isEmpty()) {
beanName = aliases.remove(0);
if (logger.isTraceEnabled()) {
logger.trace("No XML 'id' specified - using '" + beanName +
"' as bean name and " + aliases + " as aliases");
}
}
//内嵌bean,类似于java里面的匿名函数,这里是匿名bean,containingBean为父bean的名称
//如果不是匿名bean
if (containingBean == null) {
//就要检查一下beanName或aliases是否在容器中已经使用过了
checkNameUniqueness(beanName, aliases, ele);
}
//将属性转为AbstractBeanDefinition,beanDefinition有多种类型,同样不在本文的讨论范围
//我们只需要知道beanDefinition是用来存储bean的元数据即可
//其中在本文讨论访问内的是bean对应的名称、全限定名class名或class对象
//当然这个bean描述对象还保存是否有父类,是否是抽象类、泛型类、接口类,单例或原型,是否懒加载等等,
//这些不是说不重要,只是不在本文讨论范围内而已
AbstractBeanDefinition beanDefinition = parseBeanDefinitionElement(ele, beanName, containingBean);
if (beanDefinition != null) {
if (!StringUtils.hasText(beanName)) {
try {
if (containingBean != null) {
//生成可以注册到XMLBeanFactory的bean名称
//这个名称和刚刚的id不同,这个的名称还包括了一下内部类等特殊类的特殊命名
beanName = BeanDefinitionReaderUtils.generateBeanName(
beanDefinition, this.readerContext.getRegistry(), true);
}
//省略不是太重要的代码...
}
//省略不是太重要的代码...
}
String[] aliasesArray = StringUtils.toStringArray(aliases);
//包装者(装饰者)模式,在原来BeanDefinition的基础上,增加了经过修饰的bean名称,别名数组等信息
return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
}
return null;
}
}
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);这段代码给了开发者一次修饰BeanDefinition的机会,不在本文讨论范围之内,主要用于在xml中难以描述的情况。
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());这段代码则是将我们已经解析好的bdHolder注册到XmlBeanFactory中
public static void registerBeanDefinition(
BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
throws BeanDefinitionStoreException {
// 经过处理的bean名称
String beanName = definitionHolder.getBeanName();
//将BeanDefinition插入XMLBeanFactory的成员变量map中
//private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
//注入到XMLBeanFactory的成员变量 别名Map中
//private final Map<String, String> aliasMap = new ConcurrentHashMap<>(16);
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
for (String alias : aliases) {
registry.registerAlias(beanName, alias);
}
}
}
至此,我们终于完成了XmlBeanFactory的初始化,现在的XmlBeanFactory已经初始化了beanDefinitionMap、aliasMap,能够支持后面的bean实例化工作
下一章我们将继续解读第二句代码 HelloWorld helloWorld = (HelloWorld) beanFactory.getBean("helloWorld"); 敬请期待!
总结
通过阅读XmlBeanFactory的初始化,我们学习到了委托者模式、模板模式在Spring中的运用,通过对象职责的层层委托,每个对象的职责渐渐变得分明。