为什么要读源码

  • Spring是一群优秀的框架组成的社区、现在已经非常丰富了。当我们享受着Spring带来的便利同时,有时也想一探究竟。
  • 人人都说Spring好,难免有人趋之若鹜,如果让你说出个究竟,你能说出多少来?就我而言,除了能拽两AOP、DI等耳熟能详的洋词以外,就很难有高深的见解了。
  • 不得不说,选择先读Spring源码,是受到人云亦云的影响,既然都说他好,我们就要一探究竟。如果好,就要说出好的道理,如果不好,还要指出缺陷和不足。做不到这点,就算不上实事求是,就是“皇帝的新衣”了。
  • 有点儿好奇
  • 想写写东西。除了敲代码,偶尔写点别的东西,可以让我放松,也能装出很认真的样子-_-!

怎么读呢

当你下载好源码,也编译了,接下来就是欣赏源码了。可是突然发现,那画面太美以至于无法直视。怎么看,从哪里看?像对待一位亭亭玉立的处女一样,手足无措。

一切从实际出发

如果真有一位美女在你面前,你会做什么?答案是:在你没有搞清楚你和她的关系之前,你什么也不敢做。
对待源码也是这样。我用Spring源码,最多的地方莫过于启动项目的时候注入实例,包括Spring测试的时候常写的那句代码

ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("bean.xml");

所以对我而言,Spring怎么启动并完成初始化,是我首先感兴趣的。这是我和Spring的直接关系

ClassPathXmlApplicationContext是什么

先看一张类图

看一下这张图,上图最底部的ClassPathXmlApplicationContext就是我关注的Context,Context是什么意思?一般翻译为“上下文”,我认为这点比较恶心,明显词不达意。我认为就叫“容器”更准确,这点我持保留意见,不一定有人认同。

顾名思义,ClassPathXmlApplicationContext的意思就是“类路径下的Xml形式的应用容器”。这个容器辈分很低,有很多的祖先。对于客户端来说,第一种初始化容器的方式,就是创建ClassPathXmlApplicationContext的实例,并且传入一个xml格式的配置文件名称,这个配置文件需要放在classpath路径下。

 new ClassPathXmlApplicationContext("bean.xml");

这里可以大胆猜测,ClassPathXmlApplicationContext的构造方法是一个复杂的过程,这个过程就是开启我们spring源码探险的征程。

ClassPathXmlApplicationContext的构造方法

我怀着激动无比的心情,ctrl+鼠标左键,点进了ClassPathXmlApplicationContext的构造方法中,看到了这个

    /*** Create a new ClassPathXmlApplicationContext, loading the definitions* from the given XML file and automatically refreshing the context.* @param configLocation resource location* @throws BeansException if context creation failed*/public ClassPathXmlApplicationContext(String configLocation) throws BeansException {this(new String[] {configLocation}, true, null);}

构造函数的重载,是常见的复用手段,这个技巧我也经常使用。上述代码值得注意的细节如下:

1、复用的构造函数有3个参数,我们的xml配置文件作为String数组的一个元素继续传递。是不是可以猜测 “spring的配置文件其实是支持多个的”。2、第二个参数的是true,如果用idea的话,可以看到这个参数的名字叫“refresh”。一个boolean类型的变量叫refresh是否很容易猜到,这个参数其实是控制这个容器是否“重新刷新”的。至于重新刷新是什么,就目前而言我们是不清楚的

显然这里没有看到我想要的内容,于是我继续点进去,看到了:

    /*** Create a new ClassPathXmlApplicationContext with the given parent,* loading the definitions from the given XML files.* @param configLocations array of resource locations* @param refresh whether to automatically refresh the context,* loading all bean definitions and creating all singletons.* Alternatively, call refresh manually after further configuring the context.* @param parent the parent context* @throws BeansException if context creation failed* @see #refresh()*/public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)throws BeansException {super(parent);setConfigLocations(configLocations);if (refresh) {refresh();}}

从注释中可以看到,我们一些猜想得到的验证。

1、配置文件路径是能支持多个的
2、refresh定义:是否自动刷新容器,加载所有定义并创建所有单例。否则,在更多其他配置操作后,手动刷新容器。

疑惑也多了

parent context 父容器是什么东西?
简单猜测下,应该是一个父类,这个类也是容器。

进一步看代码,共三部分

    // 调用父类构造器super(parent);// 配置文件相关setConfigLocations(configLocations);// 如果需要,刷新容器if (refresh) {refresh();}

这三部分就是ClassPathXmlApplicationContext构造器的全部内容,虽然简短,但是深远。从这里我们可以看到spring源码的一个特点,复用率高,而且可读性非常好。这一点我非常佩服,我始终认为,写代码应该像写小说一样,每一个方法、变量的名字,都应该发挥它最大的作用,让看代码的人脑海中充满画面感。

supper(parent)指向何方

我在大海中,充满迷茫,这时海神波塞冬出现了,他希望我能完成一件伟大的使命,这个使命很模糊,但是第一件事情就是追寻,追寻一个起源。
supper(parent),到底指向什么?还想象不到。还有parent是什么?还不清楚,充满疑惑,我继续点击进去,竟然

    /*** Create a new AbstractXmlApplicationContext with the given parent context.* @param parent the parent context*/public AbstractXmlApplicationContext(ApplicationContext parent) {super(parent);}

离开了ClassPathXmlApplicationContext的怀抱,进入了AbstractXmlApplicationContext 。这个Context是什么,还记得那张图吗?先回头看看,然后再继续。

public abstract class AbstractXmlApplicationContext extends AbstractRefreshableConfigApplicationContext {...
}

他是ClassPathXmlApplicationContext的父类,抽象的。抽象类给我的感觉两种
1、抽象类维护一种逻辑上的结构关系。
2、抽象类通过模板方法,提高复用。
这两点,我认为第一点更重要,为了复用使用抽象类,是不明智甚至愚蠢的。

supper(parent)带着parent找到了这个抽象容器,可是它什么也不做,继续supper(parent)。对于这种做法,我渐渐有种认同:一条父子链上的所有类,往往越往上,功能越简单,甚至是个空实现。越往下,往往越具体,方法的特征也更有“具体问题具体分析”的感觉。往上,更哲学,往下,更接地气。我们要做的就是,将适度具体的方法,放在适度的层级,放的对放的准,代码的复用率就会更高。

显然,spring的作者认为,这个parent太深奥了,在AbstractXmlApplicationContext容器中做处理,有点“小材大用”了。

继续往下点击,没想到

    /*** Create a new AbstractRefreshableConfigApplicationContext with the given parent context.* @param parent the parent context*/public AbstractRefreshableConfigApplicationContext(ApplicationContext parent) {super(parent);}

又是个过手卡油的家伙,AbstractRefreshableConfigApplicationContext可以翻看前面的类图,他算是ClassPathXmlApplicationContext的爷爷了。

继续点

public AbstractRefreshableApplicationContext(ApplicationContext parent) {super(parent);}

不解释,继续

    /*** Create a new AbstractApplicationContext with the given parent context.* @param parent the parent context*/public AbstractApplicationContext(ApplicationContext parent) {this();setParent(parent);}

这里需要停一下,AbstractApplicationContext通过类图可以看到,它的地位有点特殊了,家族的人脉关系,在这里有了一个极大的丰富。
一个重要的发现是(看图):

AbstractApplicationContext往下的子孙容器,首要一个身份是ResourceLoader,即资源加载器。而Context的身份来源居然是通过接口实现的。

这一点在逻辑上是很难理解的。就好比,一天,日昏黄,你饭后坐在门口,思绪飞扬,浮想你的祖先会是什么样的一个人呢?是诗人、商人、英雄、还是普通人呢?但最终你发现,原来你的祖先不是人,而是别的什么东西?是不是感觉到了“惊悚”,我对这样的继承关系,是不太认同的。

而我对这样的继承关系,唯一能想到的解释就是——“历史原因”。如果有识之士知道其中原委,不吝赐教。
这点我还是想多说两句,我始终认为继承是要谨慎的东西。好的继承关系,就像一部家庭伦理喜剧,而不好的继承关系,就是一部乱伦史。
为了复用而继承,就好比你的同学新买了一个iphonex,而你为了玩儿上它,竟然认他做“爹”,虽然iphonex得到了复用,但是伦理丧失、逻辑全无。后人如果不知其里,定会觉得“悬疑”无比,非常的“烧脑”,科学家们也会趋之若鹜。

AbstractApplicationContext的意义在于,super(parent)止于此,我们一定要关注一下。

    this();setParent(parent);

点开this(),看到

    /*** Create a new AbstractApplicationContext with no parent.*/public AbstractApplicationContext() {this.resourcePatternResolver = getResourcePatternResolver();}

注释很清楚:创建一个没有父容器的抽象构造容器。
然后初始化了一个resourcePatternResolver(资源模板解析器)
这里用到的资源模板解析器具体实例是:

    protected ResourcePatternResolver getResourcePatternResolver() {return new PathMatchingResourcePatternResolver(this);}

叫PathMatchingResourcePatternResolver(路径匹配资源模板解析器)。而这个解析器需要一个类加载器的实例,我们给的就是this。这个this,可以理解就是我们创建的ClassPathXmlApplicationContext实例。

然后这个解析器持有了我们的资源加载器。

    /*** Create a new PathMatchingResourcePatternResolver.* <p>ClassLoader access will happen via the thread context class loader.* @param resourceLoader the ResourceLoader to load root directories and* actual resources with*/public PathMatchingResourcePatternResolver(ResourceLoader resourceLoader) {Assert.notNull(resourceLoader, "ResourceLoader must not be null");this.resourceLoader = resourceLoader;}

看完AbstractApplicationContext的this()之后,我们接着看下一行

setParent(parent);

看名字可以知道,这是添加父容器,这也是之前挖的一个坑,什么是父容器?需要点进去

    /*** {@inheritDoc}* <p>The parent {@linkplain ApplicationContext#getEnvironment() environment} is* {@linkplain ConfigurableEnvironment#merge(ConfigurableEnvironment) merged} with* this (child) application context environment if the parent is non-{@code null} and* its environment is an instance of {@link ConfigurableEnvironment}.* @see ConfigurableEnvironment#merge(ConfigurableEnvironment)*/@Overridepublic void setParent(ApplicationContext parent) {this.parent = parent;if (parent != null) {Environment parentEnvironment = parent.getEnvironment();if (parentEnvironment instanceof ConfigurableEnvironment) {getEnvironment().merge((ConfigurableEnvironment) parentEnvironment);}}}

这里没有看懂这个setParent到底干了什么,而且我们用的测试方法是

    @Testpublic void testSingleConfigLocation() {ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(FQ_SIMPLE_CONTEXT);assertTrue(ctx.containsBean("someMessageSource"));ctx.close();}

这里没有涉及到parent,所以这个坑暂时也是没有办法填上了。
这样回顾ClassPathXmlApplicationContext,这三行中的第一行代码就讲完了

        super(parent);setConfigLocations(configLocations);if (refresh) {refresh();}

总结下,基本上就是两部分
1、创建一个解析器,解析器拥有容器自己的实例,而容器实际上也是个资源加载器。
2、添加父容器,这里没有涉及到

setConfigLocations(configLocations)

super(parent)之后,是设置配置文件路径的功能,点进去
其实ClassPathXmlApplicationContext的setConfigLocations(configLocations)方法是继承自AbstractRefreshableConfigApplicationContext的

    /*** Set the config locations for this application context.* <p>If not set, the implementation may use a default as appropriate.*/public void setConfigLocations(String... locations) {if (locations != null) {Assert.noNullElements(locations, "Config locations must not be null");this.configLocations = new String[locations.length];for (int i = 0; i < locations.length; i++) {this.configLocations[i] = resolvePath(locations[i]).trim();}}else {this.configLocations = null;}}

注释告诉了一个重要信息:为应用容器设置“配置路径”,如果不设置,将使用一个默认的实现。
我的设置路径是

/org/springframework/context/support/simpleContext.xml

扩展:
1、可变参数可以传入数组,这是我刚知道的事情。
2、对于数组,可以通过Assert.noNullElements方法判断是否含有元素

AbstractRefreshableConfigApplicationContext将“locations”一个个地加入到自己的configLocations当中,configLocations是“AbstractRefreshableConfigApplicationContext”的一个私有变量

private String[] configLocations;

可能正是因为这个变量,才有的AbstractRefreshableConfigApplicationContext这个容器。

在加入之前,有一个resolvePath的动作,跟进一下

    /*** Resolve the given path, replacing placeholders with corresponding* environment property values if necessary. Applied to config locations.* @param path the original file path* @return the resolved file path* @see org.springframework.core.env.Environment#resolveRequiredPlaceholders(String)*/protected String resolvePath(String path) {return getEnvironment().resolveRequiredPlaceholders(path);}

大概意思就是用相应的环境属性,替换占位符。

getEnvironment方法的实现在AbstractApplicationContext中,是AbstractRefreshableConfigApplicationContext的爷爷了,点进getEnvironment()方法看看:

    /*** {@inheritDoc}* <p>If {@code null}, a new environment will be initialized via* {@link #createEnvironment()}.*/@Overridepublic ConfigurableEnvironment getEnvironment() {if (this.environment == null) {this.environment = createEnvironment();}return this.environment;}

有一个createEnvironment()方法,点进去

    /*** Create and return a new {@link StandardEnvironment}.* <p>Subclasses may override this method in order to supply* a custom {@link ConfigurableEnvironment} implementation.*/protected ConfigurableEnvironment createEnvironment() {return new StandardEnvironment();}

注释大意:
创建并返回一个标准环境(StandardEnvironment)
子类如果想提供一个自定义的“配置环境(ConfigurableEnvironment)”,继承这个方法。

那么StandardEnvironment()是什么?还是不太清楚,看一张图

原来StandardEnvironment是一个PropertyResolver,可以看下这个PropertyResolver是什么

/*** Interface for resolving properties against any underlying source.** @author Chris Beams* @author Juergen Hoeller* @since 3.1* @see Environment* @see PropertySourcesPropertyResolver*/
public interface PropertyResolver {...
}

叫做属性解析器。

返回的PropertyResolver立即调用了方法

    /*** Resolve ${...} placeholders in the given text, replacing them with corresponding* property values as resolved by {@link #getProperty}. Unresolvable placeholders with* no default value will cause an IllegalArgumentException to be thrown.* @return the resolved String (never {@code null})* @throws IllegalArgumentException if given text is {@code null}* or if any placeholders are unresolvable* @see org.springframework.util.SystemPropertyUtils#resolvePlaceholders(String, boolean)*/String resolveRequiredPlaceholders(String text) throws IllegalArgumentException;

大概意思就是解析配置文件中的占位符,如果解析不了抛出异常

Spring源码解读(一)——容器是如何初始化的相关推荐

  1. Spring 源码解读第七弹!bean 标签的解析

    Spring 源码解读继续. 本文是 Spring 系列第八篇,如果小伙伴们还没阅读过本系列前面的文章,建议先看看,这有助于更好的理解本文. Spring 源码解读计划 Spring 源码第一篇开整! ...

  2. spring源码解读系列(八):观察者模式--spring监听器详解

    一.前言 在前面的文章spring源码解读系列(七)中,我们继续剖析了spring的核心refresh()方法中的registerBeanPostProcessors(beanFactory)(完成B ...

  3. Spring源码深度解析(郝佳)-学习-RMI使用及Spring源码解读

    java远程方法调用.即Java RMI(Java Remote Method Invocation),是Java编程语言里一种用于实现远程过程调用的应用程序编程接口,它使客户机上运行的程序可以调用远 ...

  4. Spring源码解析 -- SpringWeb请求映射Map初始化

    简介 在上篇文章中,大致解析了Spring如何将请求路径与处理方法进行映射,但映射相关的初始化对于我们来说还是一团迷雾 本篇文章就来探索下,请求路径和处理方法的映射,是如何进行初始化的 概览 基于上篇 ...

  5. 四、spring源码解读初始化

    4.1.什么是IOC/DI? IOC(Inversion of Control)控制反转:所谓控制反转,就是把原先我们代码里面需要实现对象创建.依赖的代码,反转给容器来帮忙实现.那么必然的我们需要创建 ...

  6. Spring源码分析——IOC容器

    1.IOC容器的概念 理解IOC容器的概念之前首先需要了解依赖翻转(又称依赖倒置)的概念 许多复杂的应用都是通过多个类之间的彼此合作实现业务逻辑的,这使得每个对象都需要管理自己与其合作对象的依赖,而如 ...

  7. spring源码解读-第二部分面想切面编程

    这是第二部分了.看这部分源码需要第一部分基础.但是不知道也没关系.spring在代码耦合上控制的很好. 先进行知识扫盲: JoinPoint : 大白话:就是你的bean里面的方法:一个类里面有多少个 ...

  8. 【框架源码】Spring源码底层IOC容器加入对象的方式

    1.Spring容器加入对象方式简介 使用XML配置文件 在XML配置文件中使用< bean >标签来定义Bean,通过ClassPathXmlApplicationContext等容器来 ...

  9. spring源码解读之 JdbcTemplate源码

    原文:https://blog.csdn.net/songjinbin/article/details/19857567 在Spring中,JdbcTemplate是经常被使用的类来帮助用户程序操作数 ...

最新文章

  1. sersync2 完全安装配置说明(三) ----插件基本配置和使用
  2. ASCII 编码对照表
  3. Jmeter plugins 之 Perfmon Metrics Collector(服务器性能监控)
  4. 基于预训练语言模型的文本生成研究综述
  5. Atitit.异常的设计原理与 策略处理 java 最佳实践 p93
  6. python中怎么调用sort_python中sort()方法的cmp参数
  7. 箫演奏技巧符号大全图解
  8. 麦克马斯特计算机工程专业,麦克马斯特大学计算机专业成功录取
  9. html5视频加速播放插件,Video Speed Controller Chrome(HTML5视频加速播放插件) v0.3.2 官方免费版...
  10. 分布式-全局唯一id
  11. R语言 交互式绘图echarts4r包Pictorial深探
  12. 计算机的发展经历的变革主要基于,计算机应用基础试题
  13. RNN中的Teacher Forcing
  14. mxnet-lst文件
  15. 引用 和指针 ,简单, 一怔见血
  16. 乌班图好玩的命令_Ubuntu实用命令大全
  17. 微信小程序隐藏左上角返回首页按钮
  18. thinkPHP3.2.3使用163邮箱发送邮件
  19. 计算机lad指令什么意思,LAD 文件扩展名: 它是什么以及如何打开它?
  20. emq查看状态“node emqx@127.0.0.1 not responding to pings”

热门文章

  1. [Swift]LeetCode996. 正方形数组的数目 | Number of Squareful Arrays
  2. 简历c语言项目,C/C++:如何介绍简历中的项目?
  3. 老司机 iOS 周报 #65 | 2019-04-29
  4. 102道java算法
  5. 北京考虑分时分区单双号限行预期效果遭质疑-北京-分时分区-单双号限行
  6. python输出星号等腰三角形_星号三角形Python(带输入)(Asterisk Triangle Python (with input))...
  7. 忘记CentOS登录密码
  8. 专升本——主从复合句
  9. 【Qt学习】 碰撞检测 图元绘制
  10. 海贝播放器在iso上无法识别cue文件的问题