Spring源码解读——容器是如何初始化的
Posted BaldWinf
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring源码解读——容器是如何初始化的相关的知识,希望对你有一定的参考价值。
为什么要读源码
- 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)
*/
@Override
public void setParent(ApplicationContext parent)
this.parent = parent;
if (parent != null)
Environment parentEnvironment = parent.getEnvironment();
if (parentEnvironment instanceof ConfigurableEnvironment)
getEnvironment().merge((ConfigurableEnvironment) parentEnvironment);
这里没有看懂这个setParent到底干了什么,而且我们用的测试方法是
@Test
public 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().
*/
@Override
public 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源码解读——容器是如何初始化的的主要内容,如果未能解决你的问题,请参考以下文章