采菊东篱下,悠然见南山。
前言
有一天,我翻开源码横看竖看,满屏只看到四个字,我看不懂啊。
所以是不是曾和我一样迷失在毫无头绪的源码里,在各种类和方法里翻山越岭,却如同管中窥豹。是的话,要不今晚早点睡?
呸,扯远了,写文章怎么能分神呢。
所以正当我苦恼的时候,我想起了一句话“给我一个支点我就可以撬动整个地球”,阿基米德说的。
咦,突然让我灵机一动,那撬动整个Spring源码的支点是什么呢?或者说Spring作者在开发Spring时的支点是什么呢?这么想让我开始觉得有点意思了。
如果尝试先从使用Spring的角度来看源码,好像事情也并没有那么错综复杂。
你是怎么使用Spring的,你就怎么看Spring源码!
先把镜头拉回到使用Spring后最大变化是什么?毫无疑问是改变了我们编程方式,原来是程序员自己既要定义对象,然后还要通过new
的方式创建出对象。而在Spring中则我们只需要定义Bean,然后交给Spring来创建对象(从甲乙方的角度,定义对象是提需求的甲方,实现对象是执行的乙方。那么new
的方式是即做甲方又做乙方。而Spring让程序员当了一回甲方)。
而这个动作就是 IOC(Inversion of Control
)控制反转 。不过IoC不是一种具体的技术,而是一种设计思想。
那问题来了,IoC是怎么来的?
其实IoC可以追溯到一个原则,好莱坞原则。把IOC对应到这一原则,一切都很好理解,就是在IoC中,所有的对象都是被动的,对象初始化和调用都由Spring负责。
“不要给我们打电话,我们会给你打电话(don‘t call us, we‘ll call you)”这是著名的好莱坞原则。在好莱坞,把简历递交给演艺公司后就只有回家等待。由演艺公司对整个娱乐项的完全控制,演员只能被动式的接受公司的差使,在需要的环节中,完成自己的演出。—— 好莱坞原则
Bean是Spring中的图纸
为什么要有Bean,或者说Bean是什么呢?
我们知道无论是控制反转还是依赖注入都是围绕Object
,而对应到Spring中也就是Bean;Bean其实是包装了的Object
(对象)
所以Bean是Spring中最核心的东西,Bean`好比有了图纸,才能在Spring上建各种不同的的房子、车子。
可以说一个Bean就是经过了我们定义最后由Spring所创建的Object
。
(所以对于程序而言是由一堆的Object
构成,那么在Spring所构成的程序里就是一堆Bean
)
什么是依赖注入(DI)
前面说Bean是图纸,那么我们就是拿着图纸的设计师,因为在Spring中创建Bean的工作是由Spring来承担的,所以我们要做的是定义Bean信息。如下图所示,我们通过XML,注解的方式来定义Bean的信息。
而这个我们手动的方式定义Bean的方式,我们称为是依赖注入(DI)的方式。
将程序运行需要依赖外部的资源,从外部注入到内部,而容器加载了外部的文件、对象、数据怼到程序内的对象,此时程序内外对象之间的依赖关系,就是 依赖注入 。
所以说IoC是设计思想,那么DI就是具体的实现方式。
上面我们是我们常定义的是手动方式,而汽车都有分手动挡和自动挡,那么依赖注入方式也有分。
手动方式:
- XML 资源配置元信息
- Java 注解配置元信息
- API 配置元信息
自动方式:
- Autowiring
依赖除了有按方式,也有按注入类型区分:
- Setter注入
- 构造器注入
- 接口注入
- 方法注入
但是我们在开发中定义Bean信息的方式还可以有properties,yaml
等等。
那么如果我们想要更多配置方式怎么呢?
其实Spring已经想好了,通过定义规范来约束所有需要解析操作的接口BeanDefinitionReader
。
如下图BeanDefinitionReader
类结构图所示,可以看到那些实现BeanDefinitionReader
接口的类。
XmlBeanDefinitionReader
:读取XML文件定义的Bean。PropertiesBeanDefinitionReader
:可以从属性文件Resource,Property
对象等读取Bean。GroovyBeanDefinitionReader
:可以读取Groovy
语言定义的Bean。
所以你会发现Spring中的Bean其实就是用来统一规范的格式,纵使你有不同的Bean定义方式,其原理也只不过解析方式不一样,那么只要定义Bean方式能解析成Spring读的懂得“Bean格式”,那么就能在Spring上运行。
此时如果你回望Java虚拟机中通过Class Loader
(类装载器子系统)只能加载.class
(字节码)文件格式的想法简直如出一辙。
这里不再赘述,感兴趣的朋友可以去看前不久更新的 《我从冯·诺依曼计算机体系,追溯到了JVM,一切原来如此!》
JVM通过字节码存储格式统一了所有平台,而字节码就是构成了平台无关性的基石 。如:Python、Ruby、Scala等语言只要能转为字节码格式,那么就都能运行在JVM上面。(可以说JVM野心真大!)
——《我从冯·诺依曼计算机体系,追溯到了JVM,一切原来如此!》
(所以Spring的胃口也不小,也许在不久的将来,Spring如此生猛的生态下,会不会演化成一门面向Spring开发的语言)
BeanFactory:制造Bean的工厂
当我们自定义了Bean的信息后就是来到给Spring制造Bean时,Spring提供了暴露在外获取bean的入口BeanFactory
容器,顾名思义是Bean的工厂,负责生产和管理各个bean实例。
我们进入源码可以看到开头一句注释:
The root interface for accessing a Spring bean container.
是SpringBean容器的根接口。
BeanFactory常见的实现方式类XmlBeanFactory
,可以从classpath
或文件系统等获取资源。
Resource resource = new ClassPathResource("helloWorld.xml");
BeanFactory beanFactory = new XmlBeanFactory(resource);
ApplicationContext
除了BeanFactory
你还会注意到一样具有定位Bean功能的ApplicationContext
。
如ApplicationContext
从ClassPath
获取XML配置文件资源是通过XmlApplicationContext
。
ApplicationContext resource = new ClassPathXmlApplicationContext("helloWorld");
另外还有两种常见的实现方式:
FileSystemXmlApplicationContext
:从文件系统中的XML配置文件读取上下文。XmlWebApplicationContext
:从Web应用的XML文件读取上下文。
那直接来到源码看ApplicationContext
的类结构图,可以发现它继承了多个接口,同时也继承BeanFactory
。
所以BeanFactory
可以说是最基本的IoC容器,而ApplicationContext
是它的子类,成为子类实际上就是为了有更丰富的特性和功能。
那么又会有一个问题,Bean有这么多信息,我们怎么方便的在生产Bean的时候直接使用?
BeanDefiniton
其实在到BeanFactory还会使用到BeanDefinition来描述Bean实例的信息。
在使用<bean>
标签的时,你会发现有class、scope、lazy-init
等的配置属性,其实是Spring对所管理的Bean的一系列描述,而BeanDefinition中就提供了与之相对应的beanClass、scope、lazylnit
属性。
(简单的说,BeanDefinition就是存储着我们给Bean定义的元数据)
所以 如果说Class描述的是类的信息,那么BeanDefinition描述的是对象的信息。
那么有了BeanDefinition再制造Bean的时候就可以直接使用,比如getBeanClassName
。
BeanDefinition类中开头注释的可以看到这么一段话。
A BeanDefinition describes a bean instance
BeanDefinition描述bean实例。
IoC容器的初始化过程
从上面我们知道了,BeanDefinition描述了Bean的信息,那么意味BeanDefinition装载着Bean的信息,从而加载到BeanFactory。
那么问题又来了,在IoC容器的初始化过程中BeanDefinition是怎么装载进容器里的?
BeanDefinition的定位过程
而这就得把镜头拉到Bean容器的初始化过程。而第一个过程是 定位的过程 ,我们直接来看常用的ApplicationContext的FileSystemXmlApplicationContext可以从文件读入Resource
(资源)。
FileSystemXmlApplicationContext res = new FileSystemXmlApplicationContext("helloWorld.xml");
我们可以来看ApplicationContext的实现是怎么完成这个Resource定位过程的。
继续打开类结构图一探究竟 (一颗剽悍的种子温馨提示:下图如果看不清,可以右键打开图片浏览)
可以发现很醒目的提供了ResourceLoader
对外部资源的访问接口。从而可以加载文件、网络、流中的资源。
ResourceLoader代码如下:
public interface ResourceLoader {
String CLASSPATH_URL_PREFIX = "classpath:";
Resource getResource(String var1);
@Nullable
ClassLoader getClassLoader();
}
FileSystemXmlApplicationContext继承了AbstractApplicationContext具备了ResourceLoader读入Resource定义的BeanDefinition的能力。
public FileSystemXmlApplicationContext(ApplicationContext parent) {
super(parent);
}
对于获取Resource的具体过程,可以看DefaultResourceLoader的getResource
。
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
for (ProtocolResolver protocolResolver : getProtocolResolvers()) {
Resource resource = protocolResolver.resolve(location, this);
if (resource != null) {
return resource;
}
}
if (location.startsWith("/")) {
return getResourceByPath(location);
}
else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
}
else {
try {
// Try to parse the location as a URL...
URL url = new URL(location);
return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
}
catch (MalformedURLException ex) {
return getResourceByPath(location);
}
}
}
getResource
最后返回的getResourceByPath
会被子类FileSystemXmlApplicationContext实现。而方法返回的是一个FileSystemResource对象。
protected Resource getResourceByPath(String path) {
if (path.startsWith("/")) {
path = path.substring(1);
}
return new FileSystemResource(path);
}
通过这个FileSystemResource,Spring进行了相关的I/O操作,完成了BeanDefinition的定位。
而在定位过程完成后,就为BeanDefinition创作了载入的条件,定位过程还没有将具体数据读入,而就到了需要BeanDefinitoin载入和解析中完成。以甲乙方为例,甲方提出需求,乙方执行。乙方执行前,要先拿到甲方提出需求的具体资料才行。定位过程就好比已经拿到甲方需求资料。
BeanDefinition的载入
在前面FileSystemXmlApplicationContext的初始化过程中,其实会发现有一个refresh
方法,这个方法就是在当调用容器时载入BeanDefinition的入口。
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) throws BeansException {
super(parent);
this.setConfigLocations(configLocations);
if (refresh) {
this.refresh();
}
}
refresh
对容器的启动来说是很重要的方法,可以在AbstractApplicationContext抽象类中找到refresh()
。如下所示,它实际上描述了ApplicationContext的初始化过程。有BeanFactory的更新等等。
refresh
可以说是对ApplicationContext初始化的模板。
所以整个载入的过程是把用户定义的Bean转化为IoC容器内部数据结构,而容器内部的数据结构就是BeanDefinition。
而refresh
就像重启电脑主机一样的重启容器,在建立IoC容器后,对容器的一系列准备工作,也就是初始化过程,其中就包括BeanDefinition载入。
注册BeanDefinition
第三个过程是IoC容器注册BeanDefinition的过程,我们定义的BeanDefinition信息虽然建立起了容器对应的数据形式,但此时这些数据还不能供IoC容器直接使用,还需在IoC容器中对BeanDefinition数据进行注册。
而注册的方式实际上就是通过一个Map来存储BeanDefinition。(可以看出所谓的注册,其实是为了让IoC容器更方便获取和使用)
在DefaultListableBeanFactory类中提前定义的beanDefinitionMap。
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
DefaultListableBeanFactory中实现了 BeanDefinitionRegistry的接口。这个接口定义了 registerBeanDefinition方法来将BeanDefinition向容器的注册。
关键代码并不复杂,其实就是将准备好的BeanDefinition加到Map中。(一颗剽悍的种子翻阅到这行关键代码是被遮掩在各种if判断中,最后发现就在末尾处后不由感到欣慰的笑,不愧是你)
this.beanDefinitionMap.put(beanName, beanDefinition);
完整的registerBeanDefinition方法如下,包括了处理遇到其他情况,例如遇到同名的BeanDefinition的处理。
@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException {
Assert.hasText(beanName, "Bean name must not be empty");
Assert.notNull(beanDefinition, "BeanDefinition must not be null");
if (beanDefinition instanceof AbstractBeanDefinition) {
try {
((AbstractBeanDefinition) beanDefinition).validate();
}
catch (BeanDefinitionValidationException ex) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
"Validation of bean definition failed", ex);
}
}
BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
if (existingDefinition != null) {
if (!isAllowBeanDefinitionOverriding()) {
throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
}
else if (existingDefinition.getRole() < beanDefinition.getRole()) {
// e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
if (logger.isInfoEnabled()) {
logger.info("Overriding user-defined bean definition for bean '" + beanName +
"' with a framework-generated bean definition: replacing [" +
existingDefinition + "] with [" + beanDefinition + "]");
}
}
else if (!beanDefinition.equals(existingDefinition)) {
if (logger.isDebugEnabled()) {
logger.debug("Overriding bean definition for bean '" + beanName +
"' with a different definition: replacing [" + existingDefinition +
"] with [" + beanDefinition + "]");
}
}
else {
if (logger.isTraceEnabled()) {
logger.trace("Overriding bean definition for bean '" + beanName +
"' with an equivalent definition: replacing [" + existingDefinition +
"] with [" + beanDefinition + "]");
}
}
this.beanDefinitionMap.put(beanName, beanDefinition);
}
else {
if (hasBeanCreationStarted()) {
// Cannot modify startup-time collection elements anymore (for stable iteration)
synchronized (this.beanDefinitionMap) {
this.beanDefinitionMap.put(beanName, beanDefinition);
List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
updatedDefinitions.addAll(this.beanDefinitionNames);
updatedDefinitions.add(beanName);
this.beanDefinitionNames = updatedDefinitions;
removeManualSingletonName(beanName);
}
}
else {
// Still in startup registration phase
this.beanDefinitionMap.put(beanName, beanDefinition);
this.beanDefinitionNames.add(beanName);
removeManualSingletonName(beanName);
}
this.frozenBeanDefinitionNames = null;
}
if (existingDefinition != null || containsSingleton(beanName)) {
resetBeanDefinition(beanName);
}
}
完成了BeanDefinition注册,那么就完成了IoC容器的初始化过程。意味着IoC容器已经建立了整个Bean的配置信息。而且BeanDefinition就可以通过beanDefinitionMap被获取和使用。
(不过需要注意的是,这里只是IoC容器的初始化过程,并不包含Bean依赖注入的实现。在Spring IoC设计中,Bean定义的依赖注入方式和IoC依赖注入是两个独立过程。IoC容器的依赖注入通常发生在getBean向容器索取Bean的时候。)
至此这些信息就成了容器建立依赖反转的基础。
Spring的扩展性
那么就到了IoC实例化Bean,然后使用对象,最后销毁对象的过程。
但实际上Spring框架强大的地方还在于它的扩展性,所以Spring在这整个制造Bean的过程中加入许多的扩展点,而正是这些扩展点让我们可以即操作Spring也可以在这基础上去做更多的自定义操作(可以说是为所欲为)。
Spring的扩展点:BeanFactoryPostProcessor
BeanFactoryPostProcessor接口是Spring初始化BeanFactory时对外暴露的扩展点。调用BeanFactoryPostProcess方法时,Bean刚被解析成 BeanDefinition对象,所以bean还没有实例化,就可以在bean实例化之前通过BeanFactoryPostProcessor读取配置元数据,并可以根据需要进行修改。
BeanFactoryPostProcessor是对实例之前的任何Bean之前对其进行更改。
实例化Bean
直到经过BeanFactoryPostProcessor,才终于到了我们的实例化Bean。而通常框架在实例化时并不会使用new的方式,而是使用更灵活的反射。
而反射的具体实现,我们也相当熟悉。
//获取Class对象
Class.forName("bean");
//获取构造器
getDeclareConstructor();
//创建对象
newInstance();
到了实例化后,就是到了真正对象的使用,最后销毁过程。
但是在这个过程中还有跟应用相关的许多固定工作,如建立数据库的连接,网络的连接等等。同时等到最后程序结束时相应的要销毁这些固定的工作来释放资源。
IoC容器中的Bean生命周期
所以Spring的IoC容器制定了Bean从实例化到销毁的过程。Bean容器的实现过程就是IoC管理的Bean的生命周期。
Bean的生命周期如下:
- 实例化Bean。
- 设置Bean实例属性。
- 调用Bean初始化方法。
- 应用通过IoC容器使用Bean。
- 容器关闭时的Bean销毁。
实际上在对Bean设置完属性后,已经差不多可以使用了,但是在Bean初始化方法的前后还有Bean的前置和后置处理器扩展点。
BeanPostProcessor:AOP的关键
BeanPostProcessor后置处理器有两个方法,一个是BeanPostProcessor:before
,另一个是BeanPostProcessor:after
分别用于前后执行。BeanPostProcessor跟前面的BeanFactoryProcessor一样是Spring留给我们的扩展点。但是不同是BeanFactoryProcessor是对Bean实例化之前的扩展。而BeanPostProcessor是对Bean实例化之后的扩展。
但你发现这些扩展点的特性跟AOP(面向切面)的代理很相似。e而实际上BeanPostProcessor后置处理器就是AOP实现的关键。
我们可以从继承BeanPostProcessor接口的AbstractAutoProxyCreator抽象类里找到postProcessBeforeInstantiation后置处理器的createProxy()
方法,而在此方法中找到调用了关键的代码getProxy()
方法,而这个方法定义自AopProxy接口。
AOP中的JDK
和cglib
动态代理都是继承AopProxy。
在AopProxy接口的上方注释是这样描述的。
Delegate interface for a configured AOP proxy, allowing for the creation of actual proxy objects.
用于配置的AOP代理的委托接口,允许创建实际的代理对象。
所以当你再回过头看Spring,都是由IoC所主导的,纵使Spring的核心还有AOP,但AOP是以IoC来作为基础的。AOP也只不过是IoC中的分支,而IoC才是树的主干。
最后
或许会如梦初醒,再问Spring为什么要用IOC?
从一开始改变了我们编程的方式,即符合人类永不停息追求效率以及更方便(懒),也符合软件工程永远追求的目标,降低系统之间、模块之间和对象之间的耦合度。包括了在扩展性方面提供让整个Spring生态生机勃勃的扩展点。
可以说 Spring改变了你以IoC的方式开发的同时,你的代码也交由了Spring进行管理。
最后的最后,
如果纯粹盲目的去看Spring源码,你对Spring的知识就是一块一块的碎片,既不系统,也不结构化,更别说融会贯通了。你会觉得自己需要好好地梳理脉络,系统地掌握知识。你的这种感觉一定很强烈吧。 源码本身只不过是解决问题的细节而已,而IoC思想才是主导Spring脉络的支点。不要为了捡芝麻,而丢了西瓜。
好了,这些天,就到这里了。
我是一颗剽悍的种子, 怕什么真理无穷,进一寸,有进一寸的欢喜 。感谢各位朋友的: 关注 、 点赞 、 收藏 和 评论 ,我们下回见!
创作不易,勿白嫖。
一颗剽悍的种子 | 文 【原创】