引言

上一篇关于IoC容器的详解《Spring —— IoC 容器详解》真是工程浩大,可以说Spring官网对核心中的核心IOC容器做了非常全面的使用说明,包括在《Spring揭秘》中让我一直没有成功的Method Injection,官网也解决了我的疑惑,并最终实验成功(未来会另起一篇单独对“方法注入”做以总结)。

Spring官网的容器说明虽然全面,但是对于容器内部的处理并未深入解释,因此本篇博客做理论性的补充,总结自王富强老师的《Spring揭秘》第四章——“容器背后的秘密”。而且,本篇文章在工作和面试中更具有理论性的指导意义。

一、概述

Spring容器通过某种方式加载xml(或注解、JavaConfig)中的配置数据,然后根据这些信息绑定整个系统的对象,最终组装成一个可用的基于轻量级容器的应用系统。这个过程分为两个阶段,即容器启动阶段Bean实例化阶段:

1、容器启动阶段(四步:加载、解析、组装BeanDefinition、注册):

容器刚开始启动时,首先加载配置,然后是解析并分析配置信息,将分析后的信息装配到相应的BeanDefinition,最后把将BeanDefinition注册到BeanDefinitionRegistry,启动完成。

总的来说,该阶段所做的工作可以认为是准备性的,重点更加侧重于对象管理信息的收集,一些验证性或辅助性的工作也可以在这个阶段完成。

2、Bean实例化阶段(四步:检查、实例化、装配、生命周期回调):

第一阶段后,所有的bean定义信息都通过BeanDefinition的方式注册到了BeanDefinitionRegistry中。当某个请求方通过getBean()方法明确地请求某个对象,或因依赖关系容器需要隐式地调用getBean方法时,就会触发第二阶段。

容器首先会检查所请求的对象之前是否已经初始化。如果没有,则会根据注册的BeanDefinition所提供的信息实例化被请求对象,并为其注入依赖。如果该对象实现了某些回调接口,也会根据回调接口(Aware)的要求来装配它。当该对象装配完毕之后,容器会立即将其返回请求方使用。

二、容器启动阶段的扩展

Spring提供了一种叫做BeanFactoryPostProcessor的容器扩展机制。

该机制允许我们在容器实例化对象之前,对注册到容器中的BeanDefinition进行修改。相当于在容器实现的第一阶段最后加入一道工序,让我们对最终的BeanDefinition做一些额外的操作,比如修改bean的某些属性,为bean定义增加其他信息等。

如果要自定义BeanFactoryPostProcessor,通常就需要实现该接口,因为一个容器可能拥有多个后处理器,因此可能需要同时实现Ordered接口(如果顺序确实必要)。因为Spring已经提供了几个现成的后处理器实现,因此大多数时候我们很少去实现某个后处理器。其中PropertyPlaceholderConfigurer和PropertyOverrideConfigurer是两个比较常用的BeanFactoryPostProcessor。

1、对于BeanFactory需要手动装配BeanFactoryPostProcessor。

2、对于ApplicationContext,可以自动识别配置中的BeanFactoryPostProcessor。xml配置形式如下:

<bean class=”...PropertyPlaceholderConfigurer”>
// 后处理器的一些属性
</bean>

三、bean的生命周期

容器在启动之后,并不会马上就实例化相应的bean定义。刚刚启动的容器仅仅拥有所有对象的BeanDefinition来保存实例化阶段将会用到的必要信息。

BeanFactory的getBean方法可以被客户端对象显式调用,也可以在容器内隐式调用。

隐式调用有以下两种情况:

1、对于BeanFactory来说,对象实例化默认采用延迟初始化。A依赖B,如果当程序请求A对象时,容器会检测A依赖的B是否已经实例化,如果没有会隐式调用getBean实例化B对象,这对于本次请求者是隐式的。

2、ApplicationContext启动之后会实例化所有的bean定义,在ApplicationContext的实现过程中,依然遵循Spring容器的两个阶段,只不过它会在启动阶段完成后,立刻调用所有bean定义的实例化方法getBean这就是为什么当你得到ApplicationContext类型的容器引用时,容器内所有对象已经被全部实例化完成)。

提示:AbstractBeanFactory类的getBean()方法的完整实现逻辑,和AbstractAutowiredCapableBeanFactory类的createBean()方法的全貌

3.1 Bean的实例化策略与BeanWrapper

容器在内部实现的时候,采用“策略模式”来决定采用何种方式实例化bean。

通常可以通过反射或CGLIB动态字节码生成来实例化相应的bean实例或动态生成其子类。

InstantiationStrategy接口定义了bean的实例化策略

SimpleInstantiationStrategy实现了简单的对象实例化功能,可以通过反射来实例化对象,但不支持方法注入方式的对象实例化。

CglibSubclassingInstantiationStrategy扩展了SimpleInstantiationStrategy,加入了CGLIB的动态字节码生成功能,可以动态的生成某个类的子类,满足了方法注入所需的对象实例化需求。这是容器默认采用的实例化策略。

容器只要根据相应的bean的BeanDefinition,和

CglibSubclassingInstantiationStrategy以及不同的bean定义类型,就可以返回实例化完成的对象实例。但不是直接返回构造完成的对象实例,而是以BeanWrapper对构造完成的bean进行了包裹,返回相应的BeanWrapper实例。到这里,第一步实例化结束。

BeanWrapper接口通常在Spring框架内部使用,它有一个实现类:BeanWrapperImpl,其作用是对某个bean进行“包裹”,然后对这个“包裹”的bean进行操作,比如设置或者获取bean的相应属性值。第一步结束后返回BeanWrapper实例而不是原先的对象实例,其目的就是为了第二步的“设置对象属性”。

BeanWrapper继承了PropertyAccessor接口,可以以统一的方式对对象属性进行访问,同时又继承了PropertyEditorRegistry和TypeConverter接口(间接)。当把各种PropertyEditor注册给容器时,后面就会被BeanWrapper用到。

在第一步构造完成对象之后,Spring会根据对象实例构造一个BeanWrapperImpl实例,然后将之前CustomEditorConfigurer注册的PropertyEditor复制一份给BeanWrapperImpl实例,这就是为什么BeanWrapper同时也是PropertyEditorRegistry,这样,BeanWrapper就可以完成类型转换、设置对象属性值等操作了。

以下是两段分别通过BeanWrapper和Java反射API来设置对象属性值和获取属性值的代码片段,相比于Java反射API,Spring提供的BeanWrapper操作起来更加流程简洁:

BeanWrapper方式:

Object provider = Class.forName(“package.name.FXNewsProvider”).newInstance();
Object listener = Class.forName(“...DowJonesNewsListener”).newInstance();
Object persister = Class.forName(“...DowJonesNewsPersister”).newInstance();
BeanWrapper newsProvider = new BeanWrapperImpl(provide);
newsProvider.setPropertyValue(“newsListener”, listener);
newsProvider.setPropertyValue(“newsPersister”, persister);
assertTrue(newsProvider.getWrapperedInstance() instanceof FXNewsProvider);
assertSame(provider, newsProvider.getWrapperedInstance());
assertSame(listener, newsProvider.getPropertyValue(“newsListener”));

Java反射API方式:

Object provider = Class.forName(“package.name.FXNewsProvider”).newInstance();
Object listener = Class.forName(“...DowJonesNewsListener”).newInstance();
Object persister = Class.forName(“...DowJonesNewsPersister”).newInstance();
Class providerClazz= provider.getClass();
Field listenerField = providerClazz.getField(“newsListener”);
listenerField.set(provider , listener);// 只演示listener属性设置,persister类似
assertSame(listener, listenerField.get(provider));

可以看出,Java反射API的方式在使用上相对混乱,且不便于记忆,而且还有紧随其后的各种异常需要处理(上面并未写出)。

3.2 Aware生命周期回调

当对象实例化完成并且相关属性以及依赖设置完成后,spring 容器会检查当前对象是否实现了一系列以Aware结尾的接口。如果是,则将Aware接口中规定的依赖注入给当前实例。

常见的Aware接口有:

1、BeanNameAware,容器会将bean定义对应的beanName设置到当前对象的实例。

2、BeanClassLoaderAware,容器会将对应加载当前bean的Classloader注入到当前对象实例。默认会使用加载springframework..ClassUtils类的Classloader。

3、BeanFactoryAware,BeanFactory容器会将自身设置到当前对象实例。当前对象就拥有了一个BeanFactory容器的引用,并且可以对这个容器内允许访问的对象按照需要进行访问。

这三个Aware接口只针对BeanFactory类型的容器而言,对于ApplicationContext类型的容器,也存在几个Aware相关接口。

ApplicationContext检测以下这些Aware接口并设置相关依赖的实现方式是通过BeanPostProcessor,这与前面的三个有所不同。不过设置Aware接口与BeanPostProcessor是相邻的,也可以放在一起讨论

1、ResourceLoaderAware,ApplicationContext实现了Spring的ResourceLoader接口,当容器检测到对象实现了ResourceLoaderAware接口后,会将当前ApplicationContext自身设置到对象中,这样当前对象就拥有了其所在ApplicationContext的一个引用。

2、ApplicationEventPublisherAware,ApplicationContext同样实现了ApplicationEventPublisher接口,这样,它就可以作为ApplicationEventPublisher来使用,所以,如果对象实现了ApplicationEventPublisherAware接口,容器就同样会将自身注入当前对象。

3、ApplicationContextAware,和前面一样,容器会将自身注入当前对象。

4、MessageSourceAware,和前面一样,容器会将自身注入当前对象。

3.3 BeanPostProcessor

如何与BeanFactoryPostProcessor区分?

BeanPostProcessor存在于对象实例化阶段,BeanFactoryPostProcessor存在于容器启动阶段。BeanFactoryPostProcessor会处理容器内所有符合条件的BeanDefinition,BeanPostProcessor会处理容器内所有符合条件的实例化后的对象实例。它包含两个接口方法:

postProcessBeforeInitialization(Object bean, String beanName);
postProcessAfterInitialization(Object bean, String beanName);

常见的使用场景是处理标记接口实现类,或者为当前对象提供代理实现。

ApplicationContext对应的那些Aware接口实际上就是通过BeanPostProcessor的方式进行处理的。当ApplicationContext中每个对象的实例化过程走到BeanPostProcessor前置处理这一步时,容器会检测到之前注册到容器的ApplicationContextAwareProcessor这个BeanPostProcessor的实现类,然后调用其postProcessBeforeInitialization方法,检查并设置Aware相关依赖:

if (bean instanceof EnvironmentAware) {((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment());
}
if (bean instanceof EmbeddedValueResolverAware) {((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(this.embeddedValueResolver);
}
if (bean instanceof ResourceLoaderAware) {((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);
}
if (bean instanceof ApplicationEventPublisherAware) {((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext);
}
if (bean instanceof MessageSourceAware) {((MessageSourceAware) bean).setMessageSource(this.applicationContext);
}
if (bean instanceof ApplicationContextAware) {((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);
}

3.4 自定义BeanPostProcessor

首先需要明确,BeanPostProcessor处理的是bean实例化阶段的一个过程,因此需要处理的对象必须是bean,即必须注册到ApplicationContext容器中,才能够被该接口实现类处理。

第一步:明确需要执行的操作,比如需要提前为目标bean的某个属性做转化,或赋值。然后定义该功能的标记接口,提供可以被BeanPostProcessor访问的接口方法。

第二步:让目标bean实现标记接口。

第三步:实现BeanPostProcessor接口,并注册到容器。通过instanceof关键字判断目标bean是否为标记接口的子类,如果是,则进行相关处理逻辑,最后返回bean。

注意,标记接口和BeanPostProcessor接口本身不存在直接的继承或依赖关系,另外,标记接口实际上并不是必须的,这是为了更好的限定处理逻辑的操作范围而存在的,也就是说,我们可以通过自定义的BeanPostProcessor子类,通过instanceof直接判断是否为目标bean类型的对象,然后直接处理,但这样可能会在BeanPostProcessor中暴露太多无关于处理逻辑的属性细节。标记接口很好的隔绝了BeanPostProcessor与目标bean类型之间的耦合。

3.5 InitializingBean和init-method

InitializingBean是容器内部广泛使用的一个对象生命周期标识接口。

它只有一个接口方法:afterPropertiesSet()。其作用是在对象实例化过程调用过“BeanPostProcessor的前置处理”之后,会接着检测当前对象是否实现了InitializingBean接口,如果是,则会调用afterPropertiesSet()方法进一步调整对象实例的状态。

比如,在某些情况下,某个业务对象实例化完成后,还不能处于可以使用的状态。这个时候就可以让该业务对象实现该接口,并在方法afterPropertiesSet()中完成对该业务对象的后续处理。

实现这个接口会让Spring框架带有侵入性,因此init-method属性可以替代“实现InitializingBean接口”完成初始化方法。另外,可以让bean直接定义名为init()的方法,Spring容器也会在这一阶段执行初始化逻辑。

一般,我们在集成第三方库,或者其他特殊的情况下,才会需要使用该特性。

Spring —— 容器内部逻辑相关推荐

  1. Spring容器初始化完成后执行业务逻辑的三种方式

    一  业务背景 监听应用容器启动完毕并扫描容器类特定的Dubbo服务,并把相关元数据注册到网关. 二 思路 1  在容器启动构造元数据上报到网关,影响应用启动性能: 2  监听容器启动完毕后构造元数据 ...

  2. Spring容器初始化和bean创建过程

    文章目录 Spring容器初始化过程(注解) 1.this() 初始化bean读取器和扫描器 2. register(annotatedClasses) 3 refresh()刷新上下文 前言:一直想 ...

  3. Spring Boot教程(7) – 直观地理解Spring容器

    在你学习Spring之前,你肯定听说过"控制反转"."依赖注入"."上下文"等名词,伴随着这些名词的,是一些冗长晦涩的解释,这些解释并没有什 ...

  4. idea中生成spring的 xml配置文件_【132期】面试再被问到Spring容器IOC初始化过程,就拿这篇文章砸他~...

    点击上方"Java面试题精选",关注公众号 面试刷图,查缺补漏 >>号外:往期面试题,10篇为一个单位归置到本公众号菜单栏->面试题,有需要的欢迎翻阅 阶段汇总集 ...

  5. 【spring容器启动】之bean的实例化和初始化(文末附:spring循环依赖原理)

    本次我们通过源码介绍ApplicationContext容器初始化流程,主要介绍容器内bean的实例化和初始化过程.ApplicationContext是Spring推出的先进Ioc容器,它继承了旧版 ...

  6. spring容器管理对象和new对象

    本文来说下spring容器管理对象和new对象之间的区别与联系 文章目录 流程图说明Spring注入的对象和new的对象区别 普通的创建new对象 交由spring ioc容器进行管理 Spring的 ...

  7. spring容器_Spring容器文档阅读要点记录

    Spring容器文档阅读要点记录 相关的库代码位于 org.springframework.beans 和 org.springframework.context包下面 容器的基本的接口 基本接口:B ...

  8. Spring - Spring容器概念及其初始化过程

    引言 工作4年多,做了3年的java,每个项目都用Spring,但对Spring一直都是知其然而不知其所以然.鄙人深知Spring是一个高深的框架,正好近期脱离加班的苦逼状态,遂决定从Spring的官 ...

  9. Spring容器的事件监听机制(简单明了的介绍)

    文章目录 前言 事件 1. 定义事件 2. 定义监听器 3. 定义发布器 Spring容器的事件监听机制 1.事件的继承类图 监听器的继承类图 总结 前言 上一篇我们介绍了SpringFactorie ...

最新文章

  1. PHP 计算每个月的最后一天
  2. ios textview间距_iOS 设置TextView控件内容行间距
  3. dart系列之:dart语言中的函数
  4. Android开源之行之走进zxing,轻松实现二维码扫描(二)
  5. Springmvc架构详解
  6. 搭建本地LNMP开发环境(6)-配置nginx和PHP
  7. Java 理论与实践: 您的小数点到哪里去了?
  8. php源码修改器,php之0525获取器、修改器、验证
  9. 可涂鸦音乐光立方(DIY)
  10. 键盘拆开重新安装步骤_笔记本键盘按键安装拆卸详解
  11. 单片机C语言LED点阵编程,单片机LED点阵的介绍
  12. Kernel那些事儿之内存管理(6) --- 衣带渐宽终不悔(下)
  13. 压缩包文件密码忘记了文件怎么办?
  14. Oracle中的LOB字段解读
  15. 大数据平台架构实战(二)IntelliJ IDEA搭建hadoop
  16. 作为管理人员如何处理下属工作的偏差
  17. 初学linux(-)
  18. 十二栋文化牵手万代南梦宫 重构夹机娱乐新业态
  19. 提高写文档的能力(程序员)
  20. 传送带(三分套三分)

热门文章

  1. java range类_Java即时类| range()方法与示例
  2. 单位矩阵属性(I ^ k = I)| 使用Python的线性代数
  3. Java日历的getMinimalDaysInFirstWeek()方法和示例
  4. 第 3-2 课:集合详解(下) + 面试题
  5. VisualSVNServer的使用
  6. pandas无法打开.xlsx文件,xlrd.biffh.XLRDError: Excel xlsx file; not supported
  7. Cause: org.apache.ibatis.builder.BuilderException: Error parsing SQL Mapper Configuration.
  8. ip,子网与子网掩码
  9. flink实时流遇到的问题排查——部分数据未落库redis问题
  10. 蓝桥杯 - 历届试题 - 日期问题