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是永远绕不过的话题,它的设 ...
随机推荐
- https://scrapingclub.com/exercise/basic_login/
遇到的问题:csrftoken cfduid 是在request.headers里面的,一直在找怎么在scrapy里get request.header,从scrapy shell ,then fet ...
- c#阿里云服务器发送邮件
public static void SendMailUse() { string host = "smtp.lotusest.com";// 邮件服务器smtp.163.com表 ...
- 用javaScript对页面元素进行显示和隐藏
将显示元素进行隐藏 用document.getElementById("ID名").hidden=ture;根据页面元素ID名获得页面元素值,进而将其属性设置成隐藏. 将隐藏元素进 ...
- servlet登录界面进行用户名和密码验证
一.建立LoginServlet项目并建立如下目录 二.在Login.html中编写登录界面代码 三.在css文件中新建login.css文件 四.在src文件中添加LoginServlet.java ...
- 【论文笔记】CBAM: Convolutional Block Attention Module
CBAM: Convolutional Block Attention Module 2018-09-14 21:52:42 Paper:http://openaccess.thecvf.com/co ...
- burpsuit 无法导入证书,抓取https的解决办法
想用burpsuit中转https流量,需要安装证书: 确保浏览器能访问http 后,访问:http://burp/ 点击右上角下载证书. 然后导入,这些网上都有方法. 但如果你试了后: ①提示导入失 ...
- 三 drf 认证,权限,限流,过滤,排序,分页,异常处理,接口文档,集xadmin的使用
因为接下来的功能中需要使用到登陆功能,所以我们使用django内置admin站点并创建一个管理员. python manage.py createsuperuser 创建管理员以后,访问admin站点 ...
- MongoDB基本操作(包括插入、修改、子节点排序等)
一.基本操作 1.新增文章 db.article.insert({title:"今天天气很好",content:"我们一起去春游",_id:1}) 2.新增一条 ...
- 『Python』装饰器
一.参考 作者:zhijun liu 链接:https://www.zhihu.com/question/26930016/answer/99243411 来源:知乎 建议大家去原答案浏览 二.装饰器 ...
- 『TensorFlow』读书笔记_Inception_V3_下
极为庞大的网络结构,不过下一节的ResNet也不小 线性的组成,结构大体如下: 常规卷积部分->Inception模块组1->Inception模块组2->Inception模块组3 ...