Spring 源码学习(1)—— 容器的基本实现
最近在读Spring的源码,参考的是郝佳的《Spring源码深度解析》,这里把一些学习心得分享一下,总结的地方可能还有一些不完善,希望大家指教
IoC(控制反转)是Spring的特性之一,在传统的程序中,对象的生成往往是有开发者自己来完成的,而在Spring中,这一工作交由了Spring框架完成。Spring容器可以帮助我们管理所有的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 = "myFirstBean" class="com.wuzhe.bean.TestBean" />
</beans>
在上述代码中我们定义了对于一个简单的bean的声明。Spring为我们提供了丰富的属性来支持我们各种业务的需求,但只要我们声明成这样就足够满足我们大多数的应用了。
为了能在程序中使用这个在xml声明的bean,我们可以通过如下方式:
BeanFactory bf = new XmlBeanFactory(new ClassPathResource("spring-bean.xml"));
TestBean bean = (TestBean) bf.getBean("myFirstBean");
当然在我们的程序中直接使用BeanFactory作为容器其实并不多见,我们绝大多数情况下会使用ApplicationContext作为Spring的容器,这不妨碍我们分析它的原理
这段代码实现的功能实际上就是如下三点:
1、读取配置文件
2、根据配置文件中的信息,找到对应的类的配置,并实例化
3、调用实例化后的类
对于源码的阅读,书的作者提供了一个好的方法,就是配合UML图去分析,我觉得这个方法挺好。


1、我们先从读取配置文件来看。
Spring的配置文件是通过ClassPathResource进行封装的。
inputStreamSource 封装了所有能返回InputStream的类,Resource接口则抽象了所有资源的一些公共特性,提供了如下的接口
boolean exists();
default boolean isReadable() {
return true;
}
default boolean isOpen() {
return false;
}
default boolean isFile() {
return false;
}
URL getURL() throws IOException;
URI getURI() throws IOException;
File getFile() throws IOException;
default ReadableByteChannel readableChannel() throws IOException {
return Channels.newChannel(this.getInputStream());
}
long contentLength() throws IOException;
long lastModified() throws IOException;
Resource createRelative(String var1) throws IOException;
@Nullable
String getFilename();
String getDescription();
对于不同来源的资源文件都有相应的Resource实现。我觉得在Spring框架的源码中对于已有类进行封装特别的常见。这实际上也是符合我们面对对象编程的对修改封闭,对扩展开放的这个原则。同时通过一层层的封装,把原本复杂的逻辑分摊到每一层去实现,这样更符合单一职责的设计理念,提高了可读性。
封装好了配置信息,XmlBeanFactory首先会对封装后的ClassPathResource进行读取
public XmlBeanFactory(Resource resource, BeanFactory parentBeanFactory) throws BeansException {
super(parentBeanFactory);
this.reader = new XmlBeanDefinitionReader(this);
this.reader.loadBeanDefinitions(resource);
}
配置文件的读取工作交给了XmlBeanDefinitionReader来处理,通过调用XmlBeanDefinitionReader的loadBeanDefinitions(Resource resource)方法来加载资源
public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
return this.loadBeanDefinitions(new EncodedResource(resource));
}
在这里对于Resource又进行了一层EncodedResource封装,主要是设置了Resource资源的编码和字符集
在看loadBeanDefinitions(EncodedResource encodedResource)的代码
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
Assert.notNull(encodedResource, "EncodedResource must not be null");
if (this.logger.isInfoEnabled()) {
this.logger.info("Loading XML bean definitions from " + encodedResource.getResource());
}
Set<EncodedResource> currentResources = (Set)this.resourcesCurrentlyBeingLoaded.get();
if (currentResources == null) {
currentResources = new HashSet(4);
this.resourcesCurrentlyBeingLoaded.set(currentResources);
}
if (!((Set)currentResources).add(encodedResource)) {
throw new BeanDefinitionStoreException("Detected cyclic loading of " + encodedResource + " - check your import definitions!");
} else {
int var5;
try {
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
var5 = this.doLoadBeanDefinitions(inputSource, encodedResource.getResource());
} finally {
inputStream.close();
}
} catch (IOException var15) {
throw new BeanDefinitionStoreException("IOException parsing XML document from " + encodedResource.getResource(), var15);
} finally {
((Set)currentResources).remove(encodedResource);
if (((Set)currentResources).isEmpty()) {
this.resourcesCurrentlyBeingLoaded.remove();
}
}
return var5;
}
}
在XmlBeanDefinitionReader中维护了一套当前线程中已加载资源的集合resourcesCurrentlyBeingLoaded,真正的核心处理部分在doLoadBeanDefinitions方法中
protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException {
try {
Document doc = this.doLoadDocument(inputSource, resource);
return this.registerBeanDefinitions(doc, resource);
} catch (BeanDefinitionStoreException var4) {
throw var4;
} catch (SAXParseException var5) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(), "Line " + var5.getLineNumber() + " in XML document from " + resource + " is invalid", var5);
} catch (SAXException var6) {
throw new XmlBeanDefinitionStoreException(resource.getDescription(), "XML document from " + resource + " is invalid", var6);
} catch (ParserConfigurationException var7) {
throw new BeanDefinitionStoreException(resource.getDescription(), "Parser configuration exception parsing XML from " + resource, var7);
} catch (IOException var8) {
throw new BeanDefinitionStoreException(resource.getDescription(), "IOException parsing XML document from " + resource, var8);
} catch (Throwable var9) {
throw new BeanDefinitionStoreException(resource.getDescription(), "Unexpected exception parsing XML document from " + resource, var9);
}
}
在doLoadDocument方法中使用了DefaultDocumentLoader来加载资源,对生成的Document进行校验和解析。
拿到了转换后的Document后,接下来spring会对注册对应的BeanDefinition
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
BeanDefinitionDocumentReader documentReader = this.createBeanDefinitionDocumentReader();
documentReader.setEnvironment(this.getEnvironment());
int countBefore = this.getRegistry().getBeanDefinitionCount();
documentReader.registerBeanDefinitions(doc, this.createReaderContext(resource));
return this.getRegistry().getBeanDefinitionCount() - countBefore;
}
在上面这段代码中,spring将处理Document的逻辑委托给了BeanDefinitionDocumentReader来处理,具体逻辑在 registerBeanDefinitions中
public void registerBeanDefinitions(Document doc, XmlReaderContext readerContext) {
this.readerContext = readerContext;
this.logger.debug("Loading bean definitions");
Element root = doc.getDocumentElement();
this.doRegisterBeanDefinitions(root);
}
这里获取了Document的根节点,开头的配置文件中对应的也就是beans节点。继续深入
protected void doRegisterBeanDefinitions(Element root) {
BeanDefinitionParserDelegate parent = this.delegate;
this.delegate = this.createDelegate(this.getReaderContext(), root, parent);
if(this.delegate.isDefaultNamespace(root)) {
String profileSpec = root.getAttribute("profile");
if(StringUtils.hasText(profileSpec)) {
String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec, ",; ");
if(!this.getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
return;
}
}
}
this.preProcessXml(root);
this.parseBeanDefinitions(root, this.delegate);
this.postProcessXml(root);
this.delegate = parent;
}
这里首先对根节点的profile属性做了处理,profile属性实际上对应了我们的开发环境,通常企业的应用会有多种环境,比如生产环境,测试环境,如果想在同一套配置中维护多套环境,就可以用到这个参数,最常用的就是更换不同环境的数据库。在解析时会判断当前的环境和profile属性是否一致,若不一致则不用去解析当前的节点。
处理完profile属性后,到了真正解析BeanDefinition的阶段了,在这里用到了设计模式的模板方法,将整个解析过程抽象成了三个阶段解析前,解析中,解析后。这样做的好处是方便扩展,如果你想要做扩展的话,只需要继承当前类,并覆盖一下模板方法即可。
我们继续跟踪代码到parseBeanDefinitions()方法中
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)) {
this.parseDefaultElement(ele, delegate);
} else {
delegate.parseCustomElement(ele);
}
}
}
} else {
delegate.parseCustomElement(root);
}
}
spring的配置文件中总共有两种标签,一种是默认标签,还有一种是自定义标签。对于两种标签,spring的解析方式是不同的,默认标签采用的是parseDefaultElement方法,而自定义标签采用的是parseCustomElement方法。
spring判断标签是否默认是通过标签的命名空间来判断的。对于默认标签来说,它的命名空间就是"http://www.springframework.org/schema/beans"。
关于默认标签的解析会在后续继续介绍。
Spring 源码学习(1)—— 容器的基本实现的更多相关文章
- 创建ApplicationContext与BeanFactory时的区别-Spring源码学习之容器的基本实现
传送门 可以加载XML两种方法 使用 BeanFactory 加载 XML BeanFactory bf = new XmlBeanFactory(new ClassPathResource(&quo ...
- spring源码学习之容器的基本实现
最近想拿出一部分时间来学习一下spring的源码,还特意买了一本书结合来看,当然主要是学习并跟着作者的思路来踏上学习spring的源码的道路,特意在此记录一下,<spring源码深度解析> ...
- Spring源码学习之容器的基本实现(一)
前言 最近学习了<<Spring源码深度解析>>受益匪浅,本博客是对学习内容的一个总结.分享,方便日后自己复习或与一同学习的小伙伴一起探讨之用. 建议与源码配合使用,效果更嘉, ...
- spring源码学习之容器的扩展(二)
六 BeanFactory的后处理BeanFactory作为spring容器的基础,用于存放所有已经加载的bean,为了保证程序上的高扩展性,spring针对BeanFactory做了大量的扩展,比如 ...
- spring源码学习之容器的扩展(一)
在前面的章节,我们一直以BeanFactory接口以及它的默认实现XmlBeanFactory为例进行解析,但是,spring还提供了另一个接口ApplicationContext,用于扩展BeanF ...
- Spring 源码学习(一)-容器的基础结构
关注公众号,大家可以在公众号后台回复“博客园”,免费获得作者 Java 知识体系/面试必看资料 展示的代码摘取了一些核心方法,去掉一些默认设置和日志输出,还有大多数错误异常也去掉了,小伙伴想看详细代码 ...
- Spring源码学习-容器BeanFactory(四) BeanDefinition的创建-自定义标签的解析.md
写在前面 上文Spring源码学习-容器BeanFactory(三) BeanDefinition的创建-解析Spring的默认标签对Spring默认标签的解析做了详解,在xml元素的解析中,Spri ...
- Spring源码学习-容器BeanFactory(三) BeanDefinition的创建-解析Spring的默认标签
写在前面 上文Spring源码学习-容器BeanFactory(二) BeanDefinition的创建-解析前BeanDefinition的前置操作中Spring对XML解析后创建了对应的Docum ...
- Spring源码学习-容器BeanFactory(二) BeanDefinition的创建-解析前BeanDefinition的前置操作
写在前面 上文 Spring源码学习-容器BeanFactory(一) BeanDefinition的创建-解析资源文件主要讲Spring容器创建时通过XmlBeanDefinitionReader读 ...
- Spring源码学习-容器BeanFactory(一) BeanDefinition的创建-解析资源文件
写在前面 从大四实习至今已一年有余,作为一个程序员,一直没有用心去记录自己工作中遇到的问题,甚是惭愧,打算从今日起开始养成写博客的习惯.作为一名java开发人员,Spring是永远绕不过的话题,它的设 ...
随机推荐
- Android Studio 添加已经移除的Module
Android Studio 删除Module时,需要先在Project Structure中点击“-”来移除,此时小手机图标消失,但是这个时候Module并没有在硬盘中删除,只是和这个Project ...
- Apriori
基本概念 项与项集:设itemset={item1, item_2, …, item_m}是所有项的集合,其中,item_k(k=1,2,…,m)成为项.项的集合称为项集(itemset),包含k个项 ...
- 零基础快速入门web学习路线(含视频教程)
下面小编专门为广大web学习爱好者汇总了一条完整的自学线路:零基础快速入门web学习路线(含视频教程)(绝对纯干货)适合初学者的最新WEB前端学习路线汇总! 在当下来说web前端开发工程师可谓是高福利 ...
- 鼠标右键vsCode打开
有时候在安装vscode不会出现鼠标右键用vscode打开的情况: 最终要实现的样子: 解决办法如下步骤: 1.安装vscode: 2.新建一个“.reg”为后缀的文件: 3.把下面内容复制到文件中: ...
- vs2017 exe在Linux上运行
1:将vs .netcore控制台项目发布打包(比如文件名为:demo2core.zip,以下会用到) 2:使用XShell软件连接Linux a.在linux上使用命令 id addr找出ip地址 ...
- Redis学习-set数据结构
set 是无序集合,最大可以包含(2 的 32 次方-1)个元素.set 的是通过 hash table 实现的, 所以添加,删除,查找的复杂度都是 O(1) sadd key member 添加一个 ...
- Docker Kubernetes YAML文件常用指令
YAML文件常用指令 配置文件说明: 定义配置时,指定最新稳定版API(当前为v1). 配置文件应该存储在集群之外的版本控制仓库中.如果需要,可以快速回滚配置.重新创建和恢复. 应该使用YAML格式编 ...
- HADOOP HA 踩坑 - org.apache.hadoop.hdfs.qjournal.protocol.JournalNotFormattedException: Journal Storage Directory /mnt/data1/hadoop/dfs/journal/hdfscluster not formatted
报错:在journalnode的log中: org.apache.hadoop.hdfs.qjournal.protocol.JournalNotFormattedException: Journal ...
- linux使用代理进行apt安装 以 nord 为例
我的环境:(不必完全一样,只是提一下)----------- linux系统:kali 桌面:xface ----------------------------------------------- ...
- Windows 用bat脚本带配置启动redis,并用vb脚本使其在后台运行。
最近,在Windows上用开发PHP程序,需要用到Redis,每天要打开一个运行redis-server.exe的窗口这样比较烦,因为窗口就一直打开着,不能关闭. 所以就想着通过写脚本的方式,让他在后 ...