【Spring源码分析】Bean加载流程概览(转)
转载自:https://www.cnblogs.com/xrq730/p/6285358.html
代码入口
之前写文章都会啰啰嗦嗦一大堆再开始,进入【Spring源码分析】这个板块就直接切入正题了。
很多朋友可能想看Spring源码,但是不知道应当如何入手去看,这个可以理解:Java开发者通常从事的都是Java Web的工作,对于程序员来说,一个Web项目用到Spring,只是配置一下配置文件而已,Spring的加载过程相对是不太透明的,不太好去找加载的代码入口。
下面有很简单的一段代码可以作为Spring代码加载的入口:
1 ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml");
2 ac.getBean(XXX.class);
ClassPathXmlApplicationContext用于加载CLASSPATH下的Spring配置文件,可以看到,第二行就已经可以获取到Bean的实例了,那么必然第一行就已经完成了对所有Bean实例的加载,因此可以通过ClassPathXmlApplicationContext作为入口。为了后面便于代码阅读,先给出一下ClassPathXmlApplicationContext这个类的继承关系:
大致的继承关系是如上图所示的,由于版面的关系,没有继续画下去了,左下角的ApplicationContext应当还有一层继承关系,比较关键的一点是它是BeanFactory的子接口。
最后声明一下,本文使用的Spring版本为3.0.7,比较老,使用这个版本纯粹是因为公司使用而已。
ClassPathXmlApplicationContext存储内容
为了更理解ApplicationContext,拿一个实例ClassPathXmlApplicationContext举例,看一下里面存储的内容,加深对ApplicationContext的认识,以表格形式展现:
对象名 | 类 型 | 作 用 | 归属类 |
configResources | Resource[] | 配置文件资源对象数组 | ClassPathXmlApplicationContext |
configLocations | String[] | 配置文件字符串数组,存储配置文件路径 | AbstractRefreshableConfigApplicationContext |
beanFactory | DefaultListableBeanFactory | 上下文使用的Bean工厂 | AbstractRefreshableApplicationContext |
beanFactoryMonitor | Object | Bean工厂使用的同步监视器 | AbstractRefreshableApplicationContext |
id | String | 上下文使用的唯一Id,标识此ApplicationContext | AbstractApplicationContext |
parent | ApplicationContext | 父级ApplicationContext | AbstractApplicationContext |
beanFactoryPostProcessors | List<BeanFactoryPostProcessor> | 存储BeanFactoryPostProcessor接口,Spring提供的一个扩展点 | AbstractApplicationContext |
startupShutdownMonitor | Object | refresh方法和destory方法公用的一个监视器,避免两个方法同时执行 | AbstractApplicationContext |
shutdownHook | Thread | Spring提供的一个钩子,JVM停止执行时会运行Thread里面的方法 | AbstractApplicationContext |
resourcePatternResolver | ResourcePatternResolver | 上下文使用的资源格式解析器 | AbstractApplicationContext |
lifecycleProcessor | LifecycleProcessor | 用于管理Bean生命周期的生命周期处理器接口 | AbstractApplicationContext |
messageSource | MessageSource | 用于实现国际化的一个接口 | AbstractApplicationContext |
applicationEventMulticaster | ApplicationEventMulticaster | Spring提供的事件管理机制中的事件多播器接口 | AbstractApplicationContext |
applicationListeners | Set<ApplicationListener> | Spring提供的事件管理机制中的应用监听器 | AbstractApplicationContext |
ClassPathXmlApplicationContext构造函数
看下ClassPathXmlApplicationContext的构造函数:
1 public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
2 this(new String[] {configLocation}, true, null);
3 }
1 public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
2 throws BeansException {
3
4 super(parent);
5 setConfigLocations(configLocations);
6 if (refresh) {
7 refresh();
8 }
9 }
从第二段代码看,总共就做了三件事:
1、super(parent)
没什么太大的作用,设置一下父级ApplicationContext,这里是null
2、setConfigLocations(configLocations)
代码就不贴了,一看就知道,里面做了两件事情:
(1)将指定的Spring配置文件的路径存储到本地
(2)解析Spring配置文件路径中的${PlaceHolder}占位符,替换为系统变量中PlaceHolder对应的Value值,System本身就自带一些系统变量比如class.path、os.name、user.dir等,也可以通过System.setProperty()方法设置自己需要的系统变量
3、refresh()
这个就是整个Spring Bean加载的核心了,它是ClassPathXmlApplicationContext的父类AbstractApplicationContext的一个方法,顾名思义,用于刷新整个Spring上下文信息,定义了整个Spring上下文加载的流程。
refresh方法
上面已经说了,refresh()方法是整个Spring Bean加载的核心,因此看一下整个refresh()方法的定义:
1 public void refresh() throws BeansException, IllegalStateException {
2 synchronized (this.startupShutdownMonitor) {
3 // Prepare this context for refreshing.
4 prepareRefresh();
5
6 // Tell the subclass to refresh the internal bean factory.
7 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
8
9 // Prepare the bean factory for use in this context.
10 prepareBeanFactory(beanFactory);
11
12 try {
13 // Allows post-processing of the bean factory in context subclasses.
14 postProcessBeanFactory(beanFactory);
15
16 // Invoke factory processors registered as beans in the context.
17 invokeBeanFactoryPostProcessors(beanFactory);
18
19 // Register bean processors that intercept bean creation.
20 registerBeanPostProcessors(beanFactory);
21
22 // Initialize message source for this context.
23 initMessageSource();
24
25 // Initialize event multicaster for this context.
26 initApplicationEventMulticaster();
27
28 // Initialize other special beans in specific context subclasses.
29 onRefresh();
30
31 // Check for listener beans and register them.
32 registerListeners();
33
34 // Instantiate all remaining (non-lazy-init) singletons.
35 finishBeanFactoryInitialization(beanFactory);
36
37 // Last step: publish corresponding event.
38 finishRefresh();
39 }
40
41 catch (BeansException ex) {
42 // Destroy already created singletons to avoid dangling resources.
43 destroyBeans();
44
45 // Reset 'active' flag.
46 cancelRefresh(ex);
47
48 // Propagate exception to caller.
49 throw ex;
50 }
51 }
52 }
每个子方法的功能之后一点一点再分析,首先refresh()方法有几点是值得我们学习的:
1、方法是加锁的,这么做的原因是避免多线程同时刷新Spring上下文
2、尽管加锁可以看到是针对整个方法体的,但是没有在方法前加synchronized关键字,而使用了对象锁startUpShutdownMonitor,这样做有两个好处:
(1)refresh()方法和close()方法都使用了startUpShutdownMonitor对象锁加锁,这就保证了在调用refresh()方法的时候无法调用close()方法,反之亦然,避免了冲突
(2)另外一个好处不在这个方法中体现,但是提一下,使用对象锁可以减小了同步的范围,只对不能并发的代码块进行加锁,提高了整体代码运行的效率
3、方法里面使用了每个子方法定义了整个refresh()方法的流程,使得整个方法流程清晰易懂。这点是非常值得学习的,一个方法里面几十行甚至上百行代码写在一起,在我看来会有三个显著的问题:
(1)扩展性降低。反过来讲,假使把流程定义为方法,子类可以继承父类,可以根据需要重写方法
(2)代码可读性差。很简单的道理,看代码的人是愿意看一段500行的代码,还是愿意看10段50行的代码?
(3)代码可维护性差。这点和上面的类似但又有不同,可维护性差的意思是,一段几百行的代码,功能点不明确,不易后人修改,可能会导致“牵一发而动全身”
prepareRefresh方法
下面挨个看refresh方法中的子方法,首先是prepareRefresh方法,看一下源码:
1 /**
2 * Prepare this context for refreshing, setting its startup date and
3 * active flag.
4 */
5 protected void prepareRefresh() {
6 this.startupDate = System.currentTimeMillis();
7 synchronized (this.activeMonitor) {
8 this.active = true;
9 }
10
11 if (logger.isInfoEnabled()) {
12 logger.info("Refreshing " + this);
13 }
14 }
这个方法功能比较简单,顾名思义,准备刷新Spring上下文,其功能注释上写了:
1、设置一下刷新Spring上下文的开始时间
2、将active标识位设置为true
另外可以注意一下12行这句日志,这句日志打印了真正加载Spring上下文的Java类。
obtainFreshBeanFactory方法
obtainFreshBeanFactory方法的作用是获取刷新Spring上下文的Bean工厂,其代码实现为:
1 protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
2 refreshBeanFactory();
3 ConfigurableListableBeanFactory beanFactory = getBeanFactory();
4 if (logger.isDebugEnabled()) {
5 logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
6 }
7 return beanFactory;
8 }
其核心是第二行的refreshBeanFactory方法,这是一个抽象方法,有AbstractRefreshableApplicationContext和GenericApplicationContext这两个子类实现了这个方法,看一下上面ClassPathXmlApplicationContext的继承关系图即知,调用的应当是AbstractRefreshableApplicationContext中实现的refreshBeanFactory,其源码为:
protected final void refreshBeanFactory() throws BeansException {
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory);
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
这段代码的核心是第7行,这行点出了DefaultListableBeanFactory这个类,这个类是构造Bean的核心类,这个类的功能会在下一篇文章中详细解读,首先给出DefaultListableBeanFactory的继承关系图:
AbstractAutowireCapableBeanFactory这个类的继承层次比较深,版面有限,就没有继续画下去了,本图基本上清楚地展示了DefaultListableBeanFactory的层次结构。
为了更清晰地说明DefaultListableBeanFactory的作用,列举一下DefaultListableBeanFactory中存储的一些重要对象及对象中的内容,DefaultListableBeanFactory基本就是操作这些对象,以表格形式说明:
对象名 | 类 型 | 作 用 | 归属类 |
aliasMap | Map<String, String> | 存储Bean名称->Bean别名映射关系 | SimpleAliasRegistry |
singletonObjects | Map<String, Object> | 存储单例Bean名称->单例Bean实现映射关系 | DefaultSingletonBeanRegistry |
singletonFactories | Map<String, ObjectFactory> | 存储Bean名称->ObjectFactory实现映射关系 | DefaultSingletonBeanRegistry |
earlySingletonObjects | Map<String, Object> | 存储Bean名称->预加载Bean实现映射关系 | DefaultSingletonBeanRegistry |
registeredSingletons | Set<String> | 存储注册过的Bean名 | DefaultSingletonBeanRegistry |
singletonsCurrentlyInCreation | Set<String> | 存储当前正在创建的Bean名 | DefaultSingletonBeanRegistry |
disposableBeans | Map<String, Object> |
存储Bean名称->Disposable接口实现Bean实现映射关系 |
DefaultSingletonBeanRegistry |
factoryBeanObjectCache | Map<String, Object> | 存储Bean名称->FactoryBean接口Bean实现映射关系 | FactoryBeanRegistrySupport |
propertyEditorRegistrars | Set<PropertyEditorRegistrar> | 存储PropertyEditorRegistrar接口实现集合 | AbstractBeanFactory |
embeddedValueResolvers | List<StringValueResolver> | 存储StringValueResolver(字符串解析器)接口实现列表 | AbstractBeanFactory |
beanPostProcessors | List<BeanPostProcessor> | 存储 BeanPostProcessor接口实现列表 | AbstractBeanFactory |
mergedBeanDefinitions | Map<String, RootBeanDefinition> | 存储Bean名称->合并过的根Bean定义映射关系 | AbstractBeanFactory |
alreadyCreated | Set<String> | 存储至少被创建过一次的Bean名集合 | AbstractBeanFactory |
ignoredDependencyInterfaces | Set<Class> | 存储不自动装配的接口Class对象集合 | AbstractAutowireCapableBeanFactory |
resolvableDependencies | Map<Class, Object> | 存储修正过的依赖映射关系 | DefaultListableBeanFactory |
beanDefinitionMap | Map<String, BeanDefinition> | 存储Bean名称-->Bean定义映射关系 | DefaultListableBeanFactory |
beanDefinitionNames | List<String> | 存储Bean定义名称列表 | DefaultListableBeanFactory |
我不能保证写的每个地方都是对的,但是至少能保证不复制、不黏贴,保证每一句话、每一行代码都经过了认真的推敲、仔细的斟酌。每一篇文章的背后,希望都能看到自己对于技术、对于生活的态度。
我相信乔布斯说的,只有那些疯狂到认为自己可以改变世界的人才能真正地改变世界。面对压力,我可以挑灯夜战、不眠不休;面对困难,我愿意迎难而上、永不退缩。
其实我想说的是,我只是一个程序员,这就是我现在纯粹人生的全部。
【Spring源码分析】Bean加载流程概览(转)的更多相关文章
- Spring源码分析之-加载IOC容器
本文接上一篇文章 SpringIOC 源码,控制反转前的处理(https://mp.weixin.qq.com/s/9RbVP2ZQVx9-vKngqndW1w) 继续进行下面的分析 首先贴出 Spr ...
- 【Spring源码分析】配置文件读取流程
前言 Spring配置文件读取流程本来是和http://www.cnblogs.com/xrq730/p/6285358.html一文放在一起的,这两天在看Spring自定义标签的时候,感觉对Spri ...
- 【Spring源码分析】Bean加载流程概览
代码入口 之前写文章都会啰啰嗦嗦一大堆再开始,进入[Spring源码分析]这个板块就直接切入正题了. 很多朋友可能想看Spring源码,但是不知道应当如何入手去看,这个可以理解:Java开发者通常从事 ...
- Spring源码分析:Bean加载流程概览及配置文件读取
很多朋友可能想看Spring源码,但是不知道应当如何入手去看,这个可以理解:Java开发者通常从事的都是Java Web的工作,对于程序员来说,一个Web项目用到Spring,只是配置一下配置文件而已 ...
- Spring Boot源码分析-配置文件加载原理
在Spring Boot源码分析-启动过程中我们进行了启动源码的分析,大致了解了整个Spring Boot的启动过程,具体细节这里不再赘述,感兴趣的同学可以自行阅读.今天让我们继续阅读源码,了解配置文 ...
- 精尽Spring Boot源码分析 - 配置加载
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读 Sprin ...
- mybatis源码分析--如何加载配置及初始化
简介 Mybatis 是一个持久层框架,它对 JDBC 进行了高级封装,使我们的代码中不会出现任何的 JDBC 代码,另外,它还通过 xml 或注解的方式将 sql 从 DAO/Repository ...
- SSH 之 Spring的源码(一)——Bean加载过程
看看Spring的源码,看看巨人的底层实现,拓展思路,为了更好的理解原理,看看源码,深入浅出吧.本文基于Spring 4.0.8版本. 首先Web项目使用Spring是通过在web.xml里面配置 o ...
- springboot集成mybatis源码分析-启动加载mybatis过程(二)
1.springboot项目最核心的就是自动加载配置,该功能则依赖的是一个注解@SpringBootApplication中的@EnableAutoConfiguration 2.EnableAuto ...
随机推荐
- 关于Maven打包Java Web项目以及热部署插件Jrebel的使用
Java Web/Eclipse/Maven/Tomcat 最近有个新项目是java web项目,记录一下,可能比较乱.虽然没接触过Java,但是eclipse还是用过的 初识项目 同事说,项目是ma ...
- Python Django 学习 (一) 【Django 框架初探】
1. 简介: Python下有许多款不同的 Web 框架.Django是重量级选手中最有代表性的一位.2008年9月发布第一个版本,目前的Django版本应该是2.1. 2. 本文的环境 OS : W ...
- idea 开发插件。
作者:韩梦飞沙 Author:han_meng_fei_sha 邮箱:313134555@qq.com E-mail: 313134555 @qq.com idea 开发插件. Intellij ID ...
- BZOJ.4727.[POI2017]Turysta(哈密顿路径/回路 竞赛图)
题目链接 \(Description\) 给出一个n个点的有向图,任意两个点之间有且仅一条有向边.对于每个点v,求出从v出发的一条经过点数最多,且没有重复经过同一个点一次以上的简单路径. n<= ...
- 【11.9校内测试】【倒计时1天】【ak欢乐赛】【多项式计算模拟】
然而AK失败了,就是因为这道摸你题:(最后一篇题解了吧?QAQ) Solution 模拟多项式乘法,其中的运算处理很像高精度,不过第$i$位代表的就是$x^i$前面的系数了. 好像去年的时候就讲了表达 ...
- bzoj 1095 括号序列求两点距离
大致题意: 给一棵树,每个节点最开始都是黑色,有两种操作,1.询问树中相距最远的一对黑点的距离 2.反转一个节点的颜色 一种做法: 建立出树的括号序列,类似这样: [A[B][C]],所以长度为3*n ...
- 喵哈哈村的魔法考试 Round #18 (Div.2) 题解
喵哈哈村的古怪石碑(一) 题解:暴力check一下是等比数列还是等差数列,然后输出答案即可.注意如果数据范围是1e9的话,就要快速幂了. 代码: #include <cstdio> #in ...
- mac pro 如何让终端默认运行python3.X而不是2.7
Mac版本的Python默认是2.7,安装高版本后需要修改为你安装的版本. 1,首先打开终端 open ~/.bash_profile 打开配置文件 2. 写入python的外部环境变量(本人的版本是 ...
- GeoHash原理和可视化显示
最近在做附近定位功能的产品,geohash是一个非常不错的实现方式.查询资料,发现阿里的这篇文章讲解的很好.但文中并没有给出geohash显示的工具.无奈,也没有查到类似的.只好自己简单显示一下,方便 ...
- SimpleCaptcha生成图片验证码内容为乱码
转自:https://blog.csdn.net/wlwlwlwl015/article/details/51482065 前言 报表中发现有中文乱码和中文字体不整齐(重叠)的情况,首先考虑的就是操作 ...