内容主要参考自《Spring源码深度解析》一书算是读书笔记或是原书的补充。进入正文后可能会引来各种不适毕竟阅读源码是件极其痛苦的事情。
本文主要涉及书中第六章嘚部分依照书中内容以及个人理解对Spring进行了注释,详见Github仓库:
中我们以 BeanFactory
接口以及它的实现类 XmlBeanFactory
为例分析了整个Spring加载配置文件、创建获取 bean 嘚过程。除此以外Spring还有另一个接口 ApplicationContext
,用于扩展 BeanFactory
中现有的功能下面我们就仔细来研究一下这个接口及其实现类。
首先来看一下两者在加載配置文件的写法差异:
查看其构造函数configLocation
代表了配置文件的路径,是必传的参数也可以设置多个路径,作为数组形式传入对于XML的解析和功能实现则都在 refresh()
中实现。
II. 设置配置文件路径
该函数通过 resolvePath()
解析给定的配置文件路径数组如果路径包含特殊的符号,如 ${var}那么将会搜寻配置的环境变量并进行替换。我们在IDEA中测试一下:
我们在调用的时候替换成:
解析完配置文件路径之后,保存在 ApplicationContext
成员变量 this.configLocations
中然后便能夠读取配置文件进行解析以及实现各种功能,这些都在 refresh()
中进行实现
方法中包含了本文需要讲的所有内容,每一个函数调用便是流程中的┅个环节我们先来配合注释整体把握一下:
-
初始化 Context 前的准备工作,例如对系统属性和环境变量进行准备以及验证等
在某种情况下,可能项目需要读取系统变量而系统变量的设置可能会影响系统的正确性,那么 ClassPathXmlApplicationContext
为我们提供的这个准备函数 prepareRefresh()
就显得非常必要它可以在Spring启动嘚时候提前对必须的变量进行存在性验证。
-
这一部分主要增加一些对Spring表达式语言的支持、一些属性编辑器的注册和其他一些扩展 beanFactory 琐碎细节
-
中是一个实现方法,子类可以重写进行扩展顺便提一句,postprocess 可以翻译为后期处理或后处理
-
标题注意断句!这里只是注册,真正调用是茬 getBean()
的时候
-
为 Context 初始化 Message 源,即不同语言的消息体进行国际化处理
-
留给子类重写来初始化其他的 bean。
-
初始化剩下的单实例(非惰性)
下面,僦对这12步骤一一进行细致分析
prepareRefresh 函数主要是做些准备工作,例如对系统属性及环境变量的初始化及验证
该函数感觉好像没什么用处,因為主要就是中文注释的两个函数然而 initPropertySources
是一个空函数,没有任何逻辑而 getEnvironment().validateRequiredProperties
也因为没有需要验证的属性而没做任何处理。其实不然
initPropertySources 符合Spring开放式的结构设计,给用户最大扩展Spring能力用户可以根据自身的需要重写该方法,并在该方法进行个性化的属性设置和处理;
新建我们继承嘚类的测试类:
测试结果如下如果系统并没有检测到对应 VAR 的环境变量,将抛出异常
按照之前在IDEA设置环境变量的方法,设置了 VAR 环境变量即可解决异常。
方法设置的id 由类名称加上 @ 符号以及对象的 hashCode()
:
如果需要的话,可以从 id 反序列化回 BeanFactory
对象
对于允许覆盖和允许依赖的设置這里只判断了是否为空,一开始确实肯定是空但我们哪里去设置呢?同样方式之一可以子类重写 customizeBeanFactory
方法在其中进行设置:
书中还有关于 @Qualifier 囷 @AutoWire 注解的设置,然而至少在新版Spring5去掉了相关设置代码了先留给坑位,以后看到回来补充…
中也创建了该类对象来进行读取XML并进行配置文件的加载
接口。至于 EntityResolver
的作用已经在就已经提及了
上面的函数对已有的 beanFactory 作了如下扩展:
- 增加对 SPEL 语言的支持
- 增加对属性编辑器的支持
- 设置叻依赖功能可忽略的接口
- 设置几个自动装配的特殊规则
- 增加 AspectJ 的支持(后面AOP单独研究)
- 将相关环境变量及属性注册以单例模式注册
增加SPEL语言嘚支持
Spring表达式语言全称为“Spring Expression Language”,缩写为“SpEL” SpEL是单独模块,只依赖于core模块不依赖于其他模块,可以单独使用
关于SpEL具体使用,移步SpEL使鼡 #{···} 作为界定符,所有在大括号内的字符串被认为是SpELSpEL的解析正是通过
注册了SpEL解析器后,便可以进行解析但Spring又在何时调用这个解析器進行解析的呢?
过程(autowireByType)中也调用了该方法读者可以在上一篇文章中进行搜索该方法出现位置。
在Spring的依赖注入的时候可以把普通属性注叺进来但是其他类型无法直接从String类型进行转换直接注入的,所以一种解决方式是添加属性注册编辑器我们先来复现一下没有属性注册編辑器转换的场景。
报出的异常便是没有用于将配置文件中String类型的日期转换为Date类型的 editors 或者是 conversion strategy后者解决方法我们将在后面提及。
Spring利用属性編辑器的解决方案包含两种方式:
① 使用自定义属性编辑器
编写自定义属性编辑器时需要继承 PropertyEditorSupport
类,重写其中的 setAsText
方法在其中实现将字符串类型转换为日期类型的逻辑。实际上就是 setAsText
方法的参数(配置文件中的字符值)经过自定义转换规则转为目标类型並保存至
将我们创建的自定义属性编辑器注入到Spring容器中注意的是,书上针对的是Spring3版本的配置Spring4版本之上 CustomEditorConfigurer
的属性 customEditors
设置参数类型已经变化,铨都传入 Class 类型即类名。
再次运行测试发现这一次成功解析转换日期类型。
那么与旧版本相比,我们不经提出疑问自定义的 DatePropertyEditor
类中成員变量 format
格式是写死的,能否在Spring中配置呢
我们修改 format=”yyyyMMdd“ 格式,发现程序测试没有报错但是转换结果已经错误。
好了有挖了个坑,以后慬了来填….
② 注册Spring自带的属性编辑器
当然Spring也为我们提供了一些自带的属性编辑器,其中就包含了日期转换相关的 CustomDateEditor
首先,我们先定义属性编辑器:
然后注册到Spring中:
方法为了弄清楚具体逻辑,我们就深入的来看下究竟什么关系
方法,也就无谈属性编辑器备注册那么又昰什么时候调用的呢?查看该方法的调用者:
看到这里后逻辑就非常的清晰了在 bean 初始化后会调用 ResourceEditorRegistrar
的 registerCustomEditors
进行属性编辑器的批量注册,注册后茬属性填充环节就可以让Spring使用这些编辑器对属性进行编辑了
我们查看一下该方法的调用者:
通过上面,我们已经知道在Spring中定义了一系列瑺用的属性编辑器能够让我们方便地进行配置如果定义的某个 bean 中的某个属性的类型不在上面的常用配置的话,才需要我们进行个性化属性编辑器的注册
我们接着这一部分(beanFactory功能扩展)的源码继续分析:
可以看到,初始化后的后处理方法没有做额外的处理直接返回 bean。我們重点看看 postProcessBeforeInitialization
方法仔细看其逻辑,可以看到主要执行了 invokeAwareInterfaces(bean)
方法我们进入看看该方法对 bean 进行何种后处理操作。
我们从 invokeAwareInterfaces
方法中我们可以看出來,实现这些 Aware
接口的 bean 在被初始化之后可以取得一些对应的资源,也就是我们在之前的文章中说的这些 bean 能够感知一些额外的信息。
添加┅些额外的信息那么这些 bean 在Spring进行依赖注入时就需要忽略了,这和我们在忽略
设置自动装配的特殊规则
Spring中有忽略某些依赖的功能自然可鉯主动注册添加一些依赖功能。
ApplicationContext 对这四个类型的属性注入规定了特殊的规则比如需要对某个 bean 进行属性注入时,其中有属性的类型为 BeanFactory.class
便會立刻将 beanFactory 的实例注入进去。我们查看
将相关环境变量及属性注册以单例模式注册
关于增加对 AspectJ 的支持在下一篇文章会介绍。在 beanFactory 功能扩展过程最后一步便是以单例模式注册一些默认的和系统环境相关的 bean,名称分别是 environment、systemProperties 和
BeanFactory
作为Spring中容器功能的基础用于存放所有已经加载的 bean,为叻保证程序上的高可扩展性Spring针对 BeanFactory
做了大量的扩展,比如我们熟悉的后置处理器 PostProcessor
就是在这里实现的
作用于第五步的初始化的前后。
接下來我们就深入分析下 BeanFactory
相关的后处理
如果想改变实际的 bean 实例(例如从配置元数据创建的对象),那最好使用 BeanPostProcessor
同样的 BeanFactoryPostProcessor
的作用域范围是容器級别的, 它只是和你所使用的容器有关如果你在容器中定义了一个
在Spring的XML配置文件中可以利用 ${····} 来引用其他配置文件中配置的值,类姒 placeHolder 功能例如:
这在项目中经常Spring配置JDBC连接信息的时候使用,我们看一下 HelloMessage
内容:
以及 User
用户信息类:
这一段的逻辑需要仔细梳理首先我们得搞清楚方法传进的两个参数分别是什么:
的处理主要分两种情况,一种是硬编码情况注册的后处理器另一种则是靠配置XML配置文件注册的後处理,两者分别通过两个参数可以直接或间接获得此外,因为 beanFactory
可能是 BeanDefinitionRegistry
实例也可能不是,如果是
BeanFactoryPostProcessor
进行处理这四种情况的处理导致了非常复杂的逻辑,我将其代码中的流程注释进行截图便于更加细致的理解。
需要提到的是对于硬编码方式手动添加的后处理器是不需偠作任何排序的,但在配置文件中读取的处理器Spring不保证读取的顺序。所以为了保证有序的要求,Spring对于后处理器的调用支持按照 PriorityOrdered
和 Ordered
的顺序调用
上文提到了 BeanFactoryPostProcessor
的调用,是已经注册好的接下来我们就探索下 BeanPostProcessor
。但这里并不是调用而是注册,真正的调用其实是在 bean 的实例化阶段進行的在上一部分 beanFactory 功能扩展的添加
注册 BeanPostProcessor
这是一个很重要的步骤,也是很多功能 BeanFactory
不支持的重要原因Spring中大部分功能都是通过后处理器的方式进行扩展的,这是Spring框架的一个特写但是在 BeanFactory
中其实并没有实现后处理器的自动注册,所以在调用的时候如果没有进行手动注册其实是不能使用的但是 ApplicationContext
中却添加了自动注册功能。
先来演示自定义的 BeanPostProcessor
的使用自定义一个后处理器,默认 BeanPostProcessor
是对所有 bean 进行处理通过第二个参数,峩们可以达到对单个实例细粒度的控制
对于 BeanPostProcessor
的处理是只需要注册不需要激活调用的,所以不用考虑什么配置文件硬编码只需要一股脑紸册进行即可,但是 this.beanPostProcessors
是一个 ArrayList
所以要保证调用时有序,注册时就应该有序这一点与
该函数主要是为Spring的国际化进行服务。
我们先来回顾Spring国際化的使用方法
假设我们正在开发一个支持多国语言的web应用程序,要求系统能够根据客户端的系统的语言类型返回对应的界面:英文的操作系统返回英文界面而中文的操作系统则返回中文界面——这便是典型的i18n国际化问题。对于有国际化要求的应用系统我们不能简单哋采用硬编码的方式编写用户界面信息、报错信息等内容,而必须为这些需要国际化的信息进行特殊处理简单来说,就是为每种语言提供一套相应的资源文件并以规范化命名的方式保存在特定的目录中,由系统自动根据客户端语言选择适合的资源文件
“国际化信息”吔称为“本地化信息”,一般需要两个条件才可以确定一个特定类型的本地化信息它们分别是“语言类型”和“国家地区的类型”。如Φ文本地化信息既有中国大陆地区的中文又有中国台湾地区、中国香港地区的中文,还有新加坡地区的中文Java通过 java.util.Locale 类表示一个本地化对潒,它允许通过语言参数和国家/地区参数创建一个确定的本地化对象
java.util.Locale 是表示语言和国家/地区信息的本地化类,它是创建国际化应用的基礎接下来我们就给出几个创建本地化对象的实例,如下代码:
的用法来简单的回顾下如下代码:
基础类实现的,允许通过资源名加载國际化资源ReloadableResourceBundleMessageSource
提供了定时刷新功能,允许在不重启系统的情况下更新资源信息StaticMessageSource
主要用于程序测试,他允许通过编程的方式提供国际化信息而
通过读取并将自定义资源文件配置记录在容器中,那么就可以在获取资源文件的时候直接使用了例如,在 AbstractApplicationContext
中的获取资源文件属性嘚方法:
这部分主要与Spring的事件监听传播有关先来看一下Spring的事件监听的简单用法。
其次定义一个监听器:
当程序运行时,Spring会将发出的 TestEvent
事件转给我们自定义的 TestListener
监听器进行进一步处理这是典型的观察者模式,可以在比较关心的事件结束后及时处理
SimpleApplicationEventMulticaster
事件广播器。按照之前的礻例我们可以猜测,广播器适用于存放监听器并在合适的时候(事件发生的时候)调用监听器。我们不妨研究一下默认的广播器 SimpleApplicationEventMulticaster
:
仔細分析代码可以推断出当Spring产生事件时,会默认使用 multicastEvent()
来广播事件调用所有监听器的 onApplicationEvent
方法进行监听器的处理。而对于每个监听器而言处悝与否则完全由自己决定。
注意我们这一步只是初始化广播器,还没有开始广播因为毕竟连监听器都没有注册。
可以看到监听器全蔀保存在 ListenerRetriever
的 applicationListeners
属性中,获取也是从中获取那么有理由推测,注册时保存也是保存在此处我们具体查看:
VIII. 初始化非延迟加载单例
代码逻辑還是较为清楚的,下面着重就三个主要流程进行研究
之前我们在提及自定义属性编辑器时,讲到一种方式将 String
转换为 Date
类型就是属性编辑器那么另一种方式就是使用Converter。同样举个例子演示一下。
首先先定义转换器实现 Converter
接口:
冻结所有 bean 的定义,说明注册的 bean 定义将不被修改或進行任何进一步的处理
上面两个都用了 volatile
关键字进行修饰,支持并发
初始化非延迟加载bean
ApplicationContext 实现的默认行为就是在启动时将所有的单例非惰性的 bean 提前进行实例化放入容器中。ApplicationContext 会提前创建所有单例 bean这样在配置文件中有任何错误就会立刻被发现,而这个实例化过程就是在
加载顺序这其实和构造、Setter混合循环依赖那里的配置顺序也就有讲究了。
在Spring中还提供了 LifeCycle
接口LifeCycle
接口中包含了 start/stop 方法,实现此接口后Spring会保证在启动的時候调用其 start 方法开始其生命周期并在Spring关闭的时候调用 stop 方法来结束生命周期。
通常用于配置一些后台程序再启动后一直运行,如对MQ进行輪询等而 ApplicationContext 的初始化最后一步就是保证了这一功能的实现。
逻辑已经很简单了如果配置了就用配置的,没有配置就创建默认的 DefaultLifecycleProcessor
关于Spring的 IOC、DI 已经差不多梳理结束,其中不解之处还是有很多有机会回顾的时候再进行补充。
Spring源码博大精深想要成为不搬砖,还是得踏踏实实写玳码…