Spring源码学习-容器BeanFactory(一) BeanDefinition的创建-解析资源文件
写在前面
从大四实习至今已一年有余,作为一个程序员,一直没有用心去记录自己工作中遇到的问题,甚是惭愧,打算从今日起开始养成写博客的习惯。作为一名java开发人员,Spring是永远绕不过的话题,它的设计精巧,代码优美,值得每一名开发人员学习阅读。
在我最开始学习javaEE时,第一次接触Spring是从一个S(Struts)S(Spring)H(Herbinate)的框架开始。由java原生开发到框架开发转换过程中,那时我的印象里Struts负责控制层,herbinate负责数据层,而Spring则是业务层。他有两个核心特点:注入对象(代码原生中的工厂类)和事务控制(当时对切面编程的狭隘理解)。
实际上Spring给我们提供的最重要的两个特性就是如此,控制反转(IOC)和切面编程(AOP)。Spring到底是如何实现这两个特性的?这个问题是我们阅读Spring源码时仔细思考的问题,一切从Spring的源头,容器的加载开始。本人工作时间较短,经验浅薄。在学习源码的过程中主要是跟随书籍《Spring源码深度解析》(郝佳著)的脚步一点一点阅读,从去年至今已经断断续续的阅读了一部分Spring源码,打算慢慢把自己的心得和看法记录下来,一作为读书笔记方便日后的查阅复习,如果能给他人带来一点帮助,欣喜至极。所有的源码皆浅尝而止,我选择我认为比较重要的部分记录下来,如果想更深一步学习,可以自己阅读或者阅读上述书籍《Spring源码深度解析》。
一.Spring源码学习-容器BeanFactory和元素SpringBean的前世今生
1.读取并解析资源文件
1.1 BeanDefinition的创建 - 解析资源文件
Resource resource = new ClassPathResource("beanFactory.xml");
BeanFactory beanFactory = new XmlBeanFactory(resource);
Student student = beanFactory.getBean(Student.class);
上面是读取一个xml文件创建一个最基础的Spring容器,并且从容器中获取一个Bean元素。在整个过程中我将其主要分为二个阶段:
- 读取资源文件,将配置信息转化为BeanDefinition缓存起来,以备后续创建实例对象。
- 根据创建信息,创建Bean并且完成依赖注入的过程。
Resource接口
首先,我们看一下Resouce这个接口的定义

不管是xml、properties、yml,Spring对外在的所有资源文件创建了统一接口Resource,这是我阅读Spring源码体会OOP编程优美之处的起始点。
Spring核心包中自身集成了很多Resource实现类,除了此处我们使用了ClassPathResource,还有常见的URLResource和FileSystemResource等等。如果我们项目中有需要定位的资源文件,也可以考虑使用上面几个类,通过InputStream inputStream = resource.getInputStream();方法获取输入流后进行我们自己的处理。
跟随XmlBeanFactory的构造函数,我们正式开始探寻Spring容器的奥秘。
private final XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(this);
public XmlBeanFactory(Resource resource) throws BeansException {
this(resource, null);
}
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
super(parentBeanFactory);
this.reader.loadBeanDefinitions(resource);
}
在构造函数互相调用的过程中,我们看到XmlBeanFactory将资源文件交给了内部的XmlBeanDefinitionReader来进行资源文件的解析。loadBeanDefinitions(...)这个方法我们往上追溯的话,就能看到它是在BeanDefinitionReader接口中定义的。

看到这个类的名称就能猜到既然有XML,那就应该还可以有其他各种文件对应的实现类,如果我们需要我们甚至可以基于JS实现一个JsBeanDefinitionReader。自己写一个简单的Spring容器,实现基础的IOC功能一直是我自我提升计划之一,后续应该会找时间去动手实现。
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return loadBeanDefinitions(new EncodedResource(resource));
}
XmlBeanDefinitionReader在拿到Resource资源后首先将资源文件进行再次编码,然后交给了同类中的命名重载方法。
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Assert.notNull(encodedResource, "EncodedResource must not be null");
if (logger.isInfoEnabled()) {
logger.info("Loading XML bean definitions from " + encodedResource.getResource());
}
//todo flash 不太理解为什么要把当前资源文件放入类中ThreadLocal的Set集合中
Set<EncodedResource> currentResources = this.resourcesCurrentlyBeingLoaded.get();
if (currentResources == null) {
currentResources = new HashSet<EncodedResource>(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
}
if (!currentResources.add(encodedResource)) {
throw new BeanDefinitionStoreException(
"Detected cyclic loading of " + encodedResource + " - check your import definitions!");
}
try {
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
//获取资源流,创建XML的资源流
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();
}
}
}
上面的方法主要是数据文件的准备阶段(但目前不太理解的是为什么要把数据文件放入类中ThreadLocal的一个Set集合中,且处理完毕时还进行了移除操作),核心逻辑交给了同类中的doLoadBeanDefinitions。
在Spring的编码风格中通常一个操作非核心的数据装饰、验证,后续收尾操作在一个方法中,而核心操作往往在装饰方法前方加do,此处即是如此。
Spring代码庞大,接口众多。源码在阅读的过程中在各个接口和实现类中来回跳转,很容易就迷失方向。所以在阅读时,我更愿意以少量多次的方式去进行,搞清楚一个阶段,再继续往下,所以对应的阅读笔记也会以这样的形式去书写吧。下一篇,再见。
Spring源码学习-容器BeanFactory(一) BeanDefinition的创建-解析资源文件的更多相关文章
- Spring源码学习-容器BeanFactory(二) BeanDefinition的创建-解析前BeanDefinition的前置操作
写在前面 上文 Spring源码学习-容器BeanFactory(一) BeanDefinition的创建-解析资源文件主要讲Spring容器创建时通过XmlBeanDefinitionReader读 ...
- Spring源码学习-容器BeanFactory(三) BeanDefinition的创建-解析Spring的默认标签
写在前面 上文Spring源码学习-容器BeanFactory(二) BeanDefinition的创建-解析前BeanDefinition的前置操作中Spring对XML解析后创建了对应的Docum ...
- Spring源码学习-容器BeanFactory(四) BeanDefinition的创建-自定义标签的解析.md
写在前面 上文Spring源码学习-容器BeanFactory(三) BeanDefinition的创建-解析Spring的默认标签对Spring默认标签的解析做了详解,在xml元素的解析中,Spri ...
- Spring源码学习-容器BeanFactory(五) Bean的创建-探寻Bean的新生之路
写在前面 上面四篇文章讲了Spring是如何将配置文件一步一步转化为BeanDefinition的整个流程,下面就到了正式创建Bean对象实例的环节了,我们一起继续学习吧. 2.初始化Bean对象实例 ...
- Spring源码学习之BeanFactory体系结构
一.BeanFactory BeanFactory是Spring IOC容器的鼻祖,是IOC容器的基础接口,所有的容器都是从它这里继承实现而来.可见其地位.BeanFactory提供了最基本的IOC容 ...
- spring源码学习五 - xml格式配置,如何解析
spring在注入bean的时候,可以通过bean.xml来配置,在xml文件中配置bean的属性,然后spring在refresh的时候,会去解析xml配置文件,这篇笔记,主要来记录.xml配置文件 ...
- Spring源码学习(2)——默认标签的解析
上一篇随笔说到Spring对于默认标签和自定义标签的解析方法是不同的,这里详细看一下Spring对于默认标签的解析. private void parseDefaultElement(Element ...
- Spring 源码学习 04:初始化容器与 DefaultListableBeanFactory
前言 在前一篇文章:创建 IoC 容器的几种方式中,介绍了四种方式,这里以 AnnotationConfigApplicationContext 为例,跟进代码,看看 IoC 的启动流程. 入口 从 ...
- spring源码学习之路---IOC初探(二)
作者:zuoxiaolong8810(左潇龙),转载请注明出处,特别说明:本博文来自博主原博客,为保证新博客中博文的完整性,特复制到此留存,如需转载请注明新博客地址即可. 上一章当中我没有提及具体的搭 ...
随机推荐
- Ubuntu下使用Sublime Text 3配置Python开发环境
因为电脑配置有些低端,所以只能使用Sublime来当作Python的IDE. 1.下载Sublime Text 3并安装 首先去官网找到64位的.tar.bz2的压缩文件下载: 使用命令或归档管理器将 ...
- Razor Page 文件
Razor Pages 所有的Razor文件都以 .cshtml 结尾.大部分Razor文件都是可浏览的,包含客户端代码和服务器端代码的混合,处理后会将HTML发送到浏览器.这些页面通常被称为“内容页 ...
- Django—模板
索引 一.模板语言 1.1 变量 1.2 标签 1.3 过滤器 1.4 自定义过滤器 1.5 注释 二.模板继承 三.HTML转义 四.CSRF 五.验证码 六.反向解析 模板 作为Web框架,Dja ...
- log4j配置,输出sql到控制台
网上的 # Global logging configuration log4j.rootLogger=ERROR, stdout # log4j.logger后面跟着的是项目dao包路径,里面全部都 ...
- 【JAVA】反射总结
反射是什么? 反射就是指程序在运行的时候可以知道一个类的自身信息. 自身信息有哪些:类方法.属性.成员变量.构造方法.包等 动态编译和静态编译 静态编译:在编译的时候进确定类型,如果绑定对象成功,n ...
- DDD - 概述 - (一)
本片将介绍以下内容: 1).DDD是什么? 2).怎么使用DDD? 3).使用DDD应该规避或者注意什么? 一.DDD是什么? 简言之:领域驱动设计(domain driven design),顾名思 ...
- redis 集群 遇坑1
redis 集群 遇坑1 redis集群需要开2个端口 一个是客户端连接端口 一个是 集群总线端口 集群总线端口 是 客户端端口 + 10000 如 客户端端口是 6380 则集群总线端口 为 163 ...
- javamail 发送、读取邮件
概述 1.邮件相关的标准 厂商所提供的 JavaMail 服务程序可以有选择地实现某些邮件协议,常见的邮件协议包括: SMTP(Simple Mail Transfer Protocol) :即简单邮 ...
- Ubuntu 下使用 Nginx 部署 .NET Core 2.0 网站
前言 本文介绍如何在 Ubuntu 16.04 服务器上安装 .NET Core 2.0 SDK.创建项目与发布,并使用 Nginx 部署 .NET Core 2.0 Web 项目. 安装 .NET ...
- pycharm创建scrapy项目教程及遇到的坑
最近学习scrapy爬虫框架,在使用pycharm安装scrapy类库及创建scrapy项目时花费了好长的时间,遇到各种坑,根据网上的各种教程,花费了一晚上的时间,终于成功,其中也踩了一些坑,现在整理 ...