引言

这个系列是我阅读Spring源码后的一个总结,会从Spring Framework框架的整体结构进行分析,不会先入为主的讲解IOC或者AOP的原理,如果读者有使用Spring的经验再好不过。鉴于每个人对源码阅读角度的不同,如果文中存在理解有误的地方希望读者能够及时提出,共同进步。文章所分析的源码基于5.0.8版本,但使用老版本理解起来问题也不大,因为在框架整体架构上变化并不多。

如果你使用Spring的时间足够长,相信在最初的开发过程中你一定使用过xml文件来加载各中bean。虽然现在基本都会通过配置文件类或者注解来进行加载,但使用xml也有它的优点,这种方式对代码的侵入性最小,而且配置第三方bean也比较方便。这篇文章通过一个图书馆的例子来讲解xml最原始的加载过程,将加载过程中涉及到的各个模块比做图书馆的各个元素,希望能加深你对Spring框架的理解。

图书馆和Spring有许多相似的地方,将图书馆比做bean工厂,从图书馆借书相当于getBean的过程,将图书馆买的书放入图书馆的过程可以类比注册bean(registerBeanDefinition)的过程,而生产图书的过程又可以类比实例化BeanDefinition的过程,是不是很相似?这里我会使用下面一段比较原始的代码来分步讲解这一过程。

ClassPathResource resource = new ClassPathResource("applicationContext.xml");
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(resource);
Object beanObject = factory.getBean("beanName");

ClassPathResource与Resource

ClassPathResource resource = new ClassPathResource("applicationContext.xml");

这一行代码比较简单,它通过一个xml文件初始化了一个Resource,相当于对xml文件做了一个包装,方便以后将xml文件转换为BeanDefinition。可以将这一过程想象成现在的图书还是一堆木头,而将这些木头搅拌成木浆只是为了后面更方便的获取制作图书的原料而已。

从源码角度来说ClassPathResource继承自Resource接口,是Spring中对资源的抽象,所有需要使用的资源在Spring中都被抽象为Resource,它提供了一系列操作资源的方法,比如获取资源的名称,资源是否存在等等。Resource接口又继承了InputStreamSource接口,在InputStreamSource中,提供了一个核心方法,这个方法将资源转换成InputStream方便后期操作。

public interface InputStreamSource {
InputStream getInputStream() throws IOException;
} public interface Resource extends InputStreamSource {
boolean exists(); URL getURL() throws IOException; String getFilename(); ......
}

BeanFactory、BeanDefinition与DefaultListableBeanFactory

DefaultListableBeanFactory factory = new DefaultListableBeanFactory();

DefaultListableBeanFactory就比较重要了,它是一个Bean工厂,相当于图书馆,所有的书都在DefaultListableBeanFactory中,而借书,买书的过程都需要通过DefaultListableBeanFactory来操作。

DefaultListableBeanFactory首先是BeanFactory接口的一个实现,BeanFactory定义了通过名称和类型获取Bean的一系列方法。

public interface BeanFactory {
Object getBean(String name) throws BeansException; <T> T getBean(Class<T> requiredType) throws BeansException; boolean containsBean(String name); ......
} public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable{}

其次从DefaultListableBeanFactory的定义还可以看到,它在继承BeanFactory接口的基础上,还实现了BeanDefinitionRegistry接口。BeanDefinitionRegistry的核心功能是对Bean的注册,注册是干嘛呢?通过图书馆来对比,BeanFactory的getBean相当于从图书馆借书,那么这些书是哪来的呢?就是通过BeanDefinitionRegistry的registerBeanDefinition方法,它相当于把书放入图书馆,而DefaultListableBeanFactory就相当于图书馆本身了。

public interface BeanDefinitionRegistry extends AliasRegistry {
void registerBeanDefinition(String beanName, BeanDefinition beanDefinition); void removeBeanDefinition(String beanName) throws NoSuchBeanDefinitionException; BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException; ......
}

在BeanDefinitionRegistry的定义中还有涉及到一个关键接口:BeanDefinition,上面说BeanDefinitionRegistry相当于把书放入图书馆,那么具体图书在图书馆中怎么表示呢?这就是BeanDefinition。BeanDefinition是Bean在Spring中的抽象,也就是说每一个Bean在Spring中都对应一个BeanDefinition,它提供了与Bean相对应的属性,并提供了操作Bean的方法。

public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
void setBeanClassName(@Nullable String beanClassName); String getBeanClassName(); void setScope(@Nullable String scope); String getScope(); ......
}

BeanDefinitionReader、BeanDefinitionDocumentReader与XmlBeanDefinitionReader

XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.loadBeanDefinitions(resource);

还是通过图书馆来类比:图书馆是(DefaultListableBeanFactory),把书(BeanDefinition)放入图书馆的能力对应(BeanDefinitionRegistry),从图书馆拿编号后的书的能力对应(BeanFactory),书的原材料对应(ClassPathResource),现在就缺把书的原材料(ClassPathResource)变成一本本书(BeanDefinition),并将它放入图书馆中了。那么谁来将原材料(ClassPathResource)变成书(BeanDefinition)并放入到图书馆(DefaultListableBeanFactory)中呢?这就是XmlBeanDefinitionReader的工作了。这一过程可以通过以下源码来分析:

public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader{}
public abstract class AbstractBeanDefinitionReader implements EnvironmentCapable, BeanDefinitionReader{} public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) {
super(registry);
} protected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) {
......
this.registry = registry;
......
} public interface BeanDefinitionReader {
BeanDefinitionRegistry getRegistry(); ResourceLoader getResourceLoader(); int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException;
......
}

首先XmlBeanDefinitionReader实现了BeanDefinitionReader接口,BeanDefinitionReader定义了一个关键方法loadBeanDefinitions(Resource resource),这个方法将resource装载到BeanDefinitionRegistry中,BeanDefinitionRegistry通过XmlBeanDefinitionReader的构造方法传入。具体loadBeanDefinitions又是怎么做的呢?再来继续查看源代码:

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
......
//通过InputStreamSource接口的定义的getInputStream方法获取InputStream
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
//将InputStream包装成InputSource
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
//将Source装载到BeanDefinitionRegistry中
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
finally {
inputStream.close();
}
......
} protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)
throws BeanDefinitionStoreException {
......
try {
//将Source包装成Document
Document doc = doLoadDocument(inputSource, resource);
//将Document装载到BeanDefinitionRegistry中
return registerBeanDefinitions(doc, resource);
}
......
} public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
//这里创建了一个DefaultBeanDefinitionDocumentReader
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
int countBefore = getRegistry().getBeanDefinitionCount(); //调用DefaultBeanDefinitionDocumentReader的registerBeanDefinitions方法将将Document装载到BeanDefinitionRegistry中
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}

首先将Resource转换为EncodedResource,然后通过getInputStream获取InputStream,调用doLoadBeanDefinitions方法来装载资源,在doLoadBeanDefinitions方法中,首先将Resource包装成Document方便操作元素节点,然后把解析并装载Document的功能委托了给BeanDefinitionDocumentReader,这里使用了一个默认的DefaultBeanDefinitionDocumentReader实现。那么可以想象DefaultBeanDefinitionDocumentReader中做了两件事:将Document解析为BeanDefinitions,然后将BeanDefinitions装载到BeanDefinitionRegistry中。

public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
logger.debug("Loading bean definitions");
//从Document中获取到Element
Element root = doc.getDocumentElement(); //具体的解析过程
doRegisterBeanDefinitions(root);
} protected void doRegisterBeanDefinitions(Element root) {
......
//前置解析,默认为空,可以重写
preProcessXml(root);
//具体的解析xml并注入到过程
parseBeanDefinitions(root, this.delegate);
//后置解析,默认为空,可以重写
postProcessXml(root);
......
} protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
......
parseDefaultElement(ele, delegate);
......
} private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
//解析"import"节点
if (delegate.nodeNameEquals(ele, IMPORT_ELEMENT)) {
importBeanDefinitionResource(ele);
}
//解析"alias"节点
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);
}
} protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
//将元素包装成BeanDefinitionHolder,方便操作BeanDefinition
BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
if (bdHolder != null) {
bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);
try {
//具体的注入方法
BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, getReaderContext().getRegistry());
}
......
}
} public static void registerBeanDefinition(
BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
throws BeanDefinitionStoreException {
......
//通过BeanDefinitionRegistry将元素注入到DefaultListableBeanFactory中
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
......
}

跟之前猜测的一样,首先通过parseBeanDefinitions方法将所有的xml节点分步解析,之后将解析后的节点包装成BeanDefinitionHolder对象,最后通过BeanDefinitionRegistry的registerBeanDefinition方法将元素注入到BeanDefinitionRegistry中。

整个解析到注入过程非常复杂,我只列出了核心步骤,从中可以看到XmlBeanDefinitionReader是怎么一步步将xml中的Bean节点变为BeanDefinition并放入到DefaultListableBeanFactory中的。还是用图书馆来类比:首先将原材料(ClassPathResource)变成纸张(Document),然后将纸张(Document)通过书籍制造工厂(BeanDefinitionDocumentReader)组装成一本本书籍(BeanDefinition),然后书籍制造工厂(BeanDefinitionDocumentReader)将一本本书籍(BeanDefinition)送到图书馆(DefaultListableBeanFactory),而XmlBeanDefinitionReader就扮演了这一整个过程的组合功能。

总结

至此,整个图书馆功能就齐全了,原材料可以造书,书可以放入图书馆,并且你也可以很方便的从图书馆借书。可以说Spring设计理念也在这一过程中得到体现,它将Bean的解析,Bean的定义,Bean的生产以及Bean的获取每一步都单独抽离开来,互不干扰,最后通过DefaultListableBeanFactory将它们整合到一起供用户使用。怎么说呢,这一过程回过头来并没有什么神奇的地方,但能清晰的将每个功能都抽象出来本身就需要非常好的抽象设计能力,而对这一过程的反复阅读与分析,一定能让你在设计抽象能力上有一定的提升。

看完后你觉得这一过程类比是否恰当呢?如果你有更贴近生活的例子,不妨留言一起探讨,共同进步。说完DefaultListableBeanFactory,在下一篇文章中将会讲讲ApplicationContext接口,并对它的部分实现类做一个简单分析。

参考资料:

  • 《Spring揭秘》
  • 《Spring源码深度解析》

Spring Framework框架解析(1)- 从图书馆示例来看xml文件的加载过程的更多相关文章

  1. Tomcat源码分析——SERVER.XML文件的加载与解析

    前言 作为Java程序员,对于Tomcat的server.xml想必都不陌生.本文基于Tomcat7.0的Java源码,对server.xml文件是如何加载和解析的进行分析. 加载 server.xm ...

  2. android sax解析xml 文件 动态加载标题

    要解决一个问题 : 问题描述为 把标题动态的加载到 listView子布局中 我们首先通过 java程序写一个把标题写到xml文件的程序.这个程序会在以后讲解. 现在截图 已经写好的xm文件格式如下 ...

  3. spring加载过程,源码带你理解从初始化到bean注入

    spring在容器启动时,容器正式初始化入口refresh()如下图 ①包括初始化FactoryBean.解析XML注册所有BeanDefinition信息  ②包括注册scope管理类  ③初始化单 ...

  4. Dubbo源码解析之SPI(一):扩展类的加载过程

    Dubbo是一款开源的.高性能且轻量级的Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用.智能容错和负载均衡,以及服务自动注册和发现. Dubbo最早是阿里公司内部的RPC框架,于 ...

  5. Spring IOC bean加载过程

    首先我们不要在学习Spring的开始产生畏难情绪.Spring没有臆想的那么高深,相反,它帮我们再项目开发中制定项目框架,简化项目开发.它的主要功能是将项目开发中繁琐的过程流程化,模式化,使用户仅在固 ...

  6. SSH 之 Spring的源码(一)——Bean加载过程

    看看Spring的源码,看看巨人的底层实现,拓展思路,为了更好的理解原理,看看源码,深入浅出吧.本文基于Spring 4.0.8版本. 首先Web项目使用Spring是通过在web.xml里面配置 o ...

  7. spring启动component-scan类扫描加载过程(转)

    文章转自 http://www.it165.net/pro/html/201406/15205.html 有朋友最近问到了 spring 加载类的过程,尤其是基于 annotation 注解的加载过程 ...

  8. 看看Spring的源码(一)——Bean加载过程

    首先Web项目使用Spring是通过在web.xml里面配置org.springframework.web.context.ContextLoaderListener初始化IOC容器的. <li ...

  9. 【Spring源码分析系列】启动component-scan类扫描加载过程

    原文地址:http://blog.csdn.net/xieyuooo/article/details/9089441/ 在spring 3.0以上大家都一般会配置一个Servelet,如下所示: &l ...

随机推荐

  1. python eval()函数的妙用和滥用

    eval()函数十分强大,官方demo解释为:将字符串str当成有效的表达式来求值并返回计算结果: >>> s='8*8' >>> eval(s) 64 >& ...

  2. .net core 新建一个web api 的步骤 初级

    1.使用VS2017 选择 .net core web应用程序. 2.选择web api(空). 3.如果需要用iis express调试,则需要修改 program.cs. 4.在Controlle ...

  3. Hibernate-ORM:03.Hibernate主键生成策略

    ------------吾亦无他,唯手熟尔,谦卑若愚,好学若饥------------- 此篇博客简单记录五种常用的主键生成策咯: 不同的主键生成策略,生成的sql语句,以及hibernate的操作都 ...

  4. SpringBoot学习:使用logback进行日志记录

    项目下载地址:http://download.csdn.NET/detail/aqsunkai/9805821 (一)pom.xml文件中引入jar: <!-- https://mvnrepos ...

  5. SharedPreferences Android

    类似iOS的NSUserDefaults,采用key-value(键值对)形式,主要用于轻量级的数据存储 public class MainActivity extends AppCompatActi ...

  6. 做模态弹框的时候,防止背景滚动方法 移动端 html5

    $(window.document).bind("touchmove", function() { return false; });

  7. 【java并发编程实战】第八章:线程池的使用

    1.线程饥饿锁 定义:在线程池中,如果任务的执行依赖其他任务,那么可能会产生线程饥饿锁.尤其是单线程线程池. 示例: public class ThreadDeadStarveTest { publi ...

  8. [nginx] OpenResty 学习手册

    OpenResty Installation Find tar.gz : https://openresty.org/cn/download.html tar -xzvf openresty-VERS ...

  9. Python——数据类型之dict

    字典,相当于一个列表,不过列表的索引是数字,字典的索引是数字或者字符串. 1.字典的访问 字典是典型的key-value结构,一个key对应着一个value,key就是索引,value就是要保存的值 ...

  10. C++STL——vector

    一.相关定义 vector 数组 随机访问迭代器 快速随机访问元素 尾部进行快速随机地插入和删除操作 特征: 能够存放任意类型: 访问vector中的任意元素或从末尾添加元素都可以在常量级时间复杂度内 ...