Spring IOC 的原理及源码
# IOC 前置知识
# 为什么要设计模式
首先我想讲一个比较抽象的概念,没有在实际项目上写过代码的小伙伴可能不知道设计模式有什么用。以下通过一个例子描述一下为什么需要设计模式
首先,我有一只笔
public class Pen {
public void draw() {
System.out.println("draw");
}
}
2
3
4
5
6
然后有一个画家用了这只笔
public class Painter {
public void usePen() {
Pen pen = new Pen();
pen.paint();
}
}
2
3
4
5
6
7
这段代码在正常情况下没有什么问题,但是,在企业生产环境中往往有很多画家要用这只笔。现在我有10000个类,Painter1,Painter2...Painter10000,他们都调用了这个笔的 paint 方法
这时候,这支笔突然换路径了。10000个画家都找不到这只笔了,如果想要正常使用这只笔,必须将10000个画家的调用路径都修改一遍。如果这么做的话程序员也不用写代码了,天天在这为画家换笔
再比如,这支笔的 paint 方法换成了 write 方法,等等等等,像这种问题我们称之为强依赖(强耦合),10000个画家都依赖与这只笔
我们需要一定的方式去解决强耦合的问题,这种方式被称之为设计模式,设计模式的目的是让程序中的某个类发生改变是其他的类不用发生巨大修改,比如下面的工厂模式
public class PenFactory {
public Pen getPen() {
return new Pen();
}
}
2
3
4
5
6
我现在有一个工厂去造这支笔,所有的画家都从这个工厂的 getPen 中获得笔,现在笔的路径改了,对我们10000个画家都不会产生什么影响,我们只要在工厂中修改一次笔的路径就行了。这样画家和笔产生了解耦
下面的 IOC 中无时无刻不体现了这种思想
# 依赖倒置原则
IOC 根据依赖倒置原则演变而来,那依赖倒置原则解决了什么问题呢?
如果是面向过程的开发,上层调用下层,上层依赖于下层,当下层剧烈变动时上层也要跟着变动,这就会导致模块的复用性降低而且大大提高了开发的成本
底层是上层的组件,依赖倒置原则是面向接口编程的一种思路。一般情况下抽象的变化概率很小,让用户程序依赖于抽象,实现的细节也依赖于抽象。即使实现细节不断变动,只要抽象不变,客户程序就不需要变化
依赖倒置原则是设计模式的六大原则之一,简单来说就是:针对接口编程,依赖于抽象而不依赖于具体
# IOC(控制反转)与 DI(依赖注入)
为什么要有 IOC 呢?
试想一下,如果一个系统有大量的组件,其生命周期和相互之间的依赖关系如果由组件自身来维护,也就是对各个类的控制权在我们手中,这样的话我们需要手动的去创建各个类并且维持他们的依赖关系,不但大大增加了系统的复杂度,而且会导致组件之间极为紧密的耦合
举个例子:现有类 A 依赖于类 B,如果没有 IOC 的话,可能需要在类 A 中手动通过 new 关键字来 new 一个 B 的对象出来。如果 B 还依赖 C 的话,C 可能也需要 new 一个 B,并且这个 B 里面还要 new 一个 A
而解决问题的方案就是 IOC。既然我们手动去操作非常麻烦,我们可以写一段代码去操作这些依赖关系啊。IOC 核心概念就是将你设计好的对象以及他们的依赖关系交给第三方容器控制。IOC 容器和 DI 让上层从被动接受一个底层组件,变成主动选择一个底层组件,这样我们就不需要去思考这些对象的依赖关系,使系统耦合度降低,所以我们得出以下结论:
- 控制:指的是对象创建(实例化、管理)的权力
- 反转:控制权交给外部环境(IOC 容器)
而所谓的依赖注入,就是把底层类作为参数传入上层类、将依赖项注入到被依赖项中,实现上层类对下层类的控制。此时就体现出依赖抽象的好处了:我们可以注入任何抽象类的子类,让程序变的更加多样性
# IOC 容器
计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决。这句话在设计模式中体现的淋漓尽致,任何强耦合的对象在他们之间加一次中间件就能解耦,IOC 容器就是负责解耦的容器,你可以把它看成是一个装对象的集合
IOC 容器负责实例化、定位、配置应用程序中的对象及建立这些对象间的依赖
IOC 容器有两个好处:一是容器管理所有的对象,我们不需要去手动创建对象;二是容器管理所有的依赖项,在创建实例的时候不需要了解其中的细节
而 Spring 对 IOC 容器的具体实现就是 BeanFactory
啥是 BeanFactory 呢,BeanFactory 是 Spring 框架中最基础的容器接口,提供了以下核心功能:
- 依赖注入:管理 bean 的创建和依赖关系
- 配置机制:支持多种配置方式(XML、注解等)
- 生命周期管理:控制 bean 的初始化和销毁
- 延迟加载:默认情况下,bean 是按需加载的(懒加载模式)
BeanFactory 会读取项目整体的配置,和所有被扫描到的类,生成一个 map 进行管理。而 ApplicationContext 是 BeanFactory 的子接口,提供了更多企业级功能:
- AOP 支持:提供声明式 AOP 功能
- 国际化:消息资源处理(i18n)
- 自动注册:自动注册 BeanPostProcessor 等特殊 bean
# Spring IOC 的大体思路
首先 IOC 容器需要知道这个类的具体位置才能找到对应的对象,而我们就用 xml 或者注解的方式标记这个类的位置
然后 Spring 容器通过反射以获得该类的对象(反射的作用是可以获取任意一个类的所有属性和方法,你还可以调用这些方法和属性)
IOC 容器中存放了程序需要的所有的对象,这些对象只会被创建一次(单例),容器将这些对象包装成一个个 bean(带有对象信息以及该对象的结构体),IOC 容器简单来说不过是一个以该对象名称为 key、对应 bean 为 value 的 map。当然,只是简单来说
IOC 容器完成创建这一步之后会根据我们所需要的方式来管理 bean 的依赖
# 构造器、注解、方法注入
Spring 官方推荐使用构造器注入,理由如下:
// 1. 构造器注入
@Service
public class OrderService {
private final OrderRepository repo;
public OrderService(OrderRepository repo) {
this.repo = repo;
}
}
// 2. Setter 注入
@Service
public class OrderService {
private OrderRepository repo;
@Autowired
public void setOrderRepository(OrderRepository repo) {
this.repo = repo;
}
}
// 3. 字段注入
@Service
public class OrderService {
@Autowired
private OrderRepository repo;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
- 1,构造器注入可以将依赖设置为 final,确保依赖的不可变性。而其他两种 java 对象创建后,依赖有可能是 null,因此 spring 内部可能在容器创建的时候做一些非空检查
- 2,构造器注入可以使依赖关系更加清晰,方便测试,可读性更好
- 3,构造器注入的话,类只是一个普通 POJO,没有任何 Spring 注解也能正常使用(自己 new)
- 4,构造器注入解决不了循环依赖,Spring 会直接抛 BeanCurrentlyInCreationException。鼓励工程师优先重构设计,消除循环依赖。实在不行可以使用 @Lazy 延迟初始化
# 源码
来看看 IOC 容器是如何实现上面的过程的
最常用的 IOC 容器是 ClassPathXmlApplicationContext,我们就从它开始,它的类继承图如下
不要害怕,比较重要的接口也只有几个
BeanFactory 接口定义了容器的最基本功能,它可以读取类的配置文档,管理类的加载,实例化,维护类之间的依赖关系。实现了此接口的类才能叫容器(实现它的容器实例化时并不会初始化配置文件中定义的类,初始化动作发生在第一次调用时)
ApplicationContext 接口除了提供容器的基本功能外还提供了很多的扩展功能(实现它的容器实例化时就会将配置文件中定义的类初始化)
其他的比较重要的接口:
ApplicationContext 继承了 ListableBeanFactory,这个 Listable 的意思就是,通过这个接口,我们可以获取多个 Bean,大家看源码会发现,最顶层 BeanFactory 接口的方法都是获取单个 Bean 的
ApplicationContext 继承了 HierarchicalBeanFactory,Hierarchical 单词本身已经能说明问题了,也就是说我们可以在应用中起多个 BeanFactory,然后可以将各个 BeanFactory 设置为父子关系
AutowireCapableBeanFactory 就是用来自动装配 Bean 用的,上图并没有显示它。但是不继承不代表不可以使用组合
# 容器创建
从创建 bean 工厂开始看看它的过程。需要重点关注的点是:refresh 重建工厂,bean 定义,别名处理,bean 覆盖
# refresh
步骤1:读取配置创建 bean 工厂
//这一行我们开始创建bean工厂
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//最终都会调用这个方法来创建工厂
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) throws BeansException {
super(parent);
//读取配置
this.setConfigLocations(configLocations);
if (refresh) {
//主要方法,调用这个方法创建容器,这个方法可以手动调用
this.refresh();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
this.refresh() 其实调用了它父类 AbstractApplicationContext 的 refresh 方法
有没有想过这个方法为什么叫 refresh 而不叫 create?
因为容器建立以后,我们可以通过调用 refresh() 方法重建,refresh() 会将原来的容器销毁,然后再重新执行一次初始化操作,因此命名为 refresh 而不是 init 或者其他名字
bean 容器构建经过了13个重要的方法,这13个方法就是整个 Spring 核心的处理流程,在里面也可以看到 bean 的生命周期
public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext {
public void refresh() throws BeansException, IllegalStateException {
synchronized(this.startupShutdownMonitor) {
/**
* 1,做容器刷新前的准备工作,这里会设置容器的启动时间、设置活跃状态为true、设置关闭状态为false、获取Environment对象,并加载当前系统的属性值到Environment对象中去、准备监听器和事件的集合对象,默认为空的集合
*/
this.prepareRefresh();
// 2,创建容器对象:DefaultListableBeanFactory,加载xml配置文件的属性值到当前工厂中,最重要的就是BeanDefinition,即bean的各种定义
ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
// 3,beanFactory的准备工作,对各种属性进行填充
this.prepareBeanFactory(beanFactory);
try {
// 4,子类覆盖方法做额外的处理,此处我们自己一般不做任何扩展工作,但是可以查看web中子类的代码是有具体实现的
this.postProcessBeanFactory(beanFactory);
// 5,调用各种beanFactory前置处理器,这里比较核心,操作对象BeanDefinition
this.invokeBeanFactoryPostProcessors(beanFactory);
// 6,注册bean处理器,上一步注册的是bean工厂的处理器,这一步处理的是bean的处理器,两者要处理的内容不一样
this.registerBeanPostProcessors(beanFactory);
// 7,为上下文初始化message源,即不同语言的消息体
this.initMessageSource();
// 8,初始化事件监听器多路广播器
this.initApplicationEventMulticaster();
// 9,留给子类来初始化其他的bean
this.onRefresh();
// 10,在所有注册的bean中查找listener bean,注册到消息广播器中
this.registerListeners();
// 11,实例化剩下的非懒加载的单实例,著名的getBean方法在该方法中被循环调用
this.finishBeanFactoryInitialization(beanFactory);
// 12,完成属性过程,通知生命周期处理器lifecycleProcessor刷新过程,同时发出ContextRefreshEvent
this.finishRefresh();
} catch (BeansException var9) {
if (this.logger.isWarnEnabled()) {
this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
}
// 为防止bean资源占用,在异常处理中,销毁已经在前面过程中生成的单件bean
this.destroyBeans();
// 重置active标志
this.cancelRefresh(var9);
throw var9;
} finally {
// 13,清空缓存,删除例如 BeanDefinition 的解析缓存、方法/字段的反射缓存、注解元数据缓存等
this.resetCommonCaches();
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
13个重要方法需要关注:
1,根据配置创建 beanFactory(bean 工厂),根据配置生成 bean 定义 2,获取并且注册 beanFactory 处理器、获取并且注册 bean 处理器 3,注册并且管理事件监听器 4,循环调用 getBean,实例化非懒加载的单实例 5,后置处理,清除缓存、发布事件等
# BeanDefinition
这个接口非常重要,配置文件被 Springxml 解析后,这里的 BeanDefinition 就是我们所说的 Spring 的 Bean,我们配置的一个个类的信息都会存放在一个个BeanDefinition中
public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
String SCOPE_SINGLETON = "singleton";
String SCOPE_PROTOTYPE = "prototype";
int ROLE_APPLICATION = 0;
.....
}
2
3
4
5
6
步骤2:注册 Bean
读取完配置之后,会以 beanname 为键,beanfefinition 为值,存放在一个 map 中,这个过程叫注册 bean
//调用此函数开始注册
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
2
别名处理:
使用 map 保持别名,遇到别名时,先把别名变成 beanName
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
for (String alias : aliases) {
registry.registerAlias(beanName, alias);
}
}
2
3
4
5
6
bean 覆盖:指多个 bean 的名字相同,默认情况下 spring 支持 bean 覆盖,在注册 bean 遇到 bean 覆盖时会这样处理
BeanDefinition oldBeanDefinition;
oldBeanDefinition = this.beanDefinitionMap.get(beanName);
if (oldBeanDefinition != null) {
if (!isAllowBeanDefinitionOverriding()) {
// 如果不允许覆盖的话,抛异常
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription()...
}
else if (oldBeanDefinition.getRole() < beanDefinition.getRole()) {
// log...用框架定义的 Bean 覆盖用户自定义的 Bean
}
else if (!beanDefinition.equals(oldBeanDefinition)) {
// log...用新的 Bean 覆盖旧的 Bean
}
else {
// log...用同等的 Bean 覆盖旧的 Bean,这里指的是 equals 方法返回 true 的 Bean
}
// 覆盖
this.beanDefinitionMap.put(beanName, beanDefinition);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
完成注册的是这些语句,会有两个集合保存信息
// 将 BeanDefinition 放到这个 map 中,这个 map 保存了所有的 BeanDefinition
this.beanDefinitionMap.put(beanName, beanDefinition);
// 这是个 ArrayList,所以会按照 bean 配置的顺序保存每一个注册的 Bean 的名字
this.beanDefinitionNames.add(beanName);
2
3
4
# invokeBeanFactoryPostProcessors
spring 容器获取 bean 定义后,不会立刻初始化 bean,而是调用 invokeBeanFactoryPostProcessors 方法做一些增强处理
如果需要在所有 bean 加载之前做一些处理,需要继承 BeanFactoryPostProcessor 接口,典型应用场景如动态注册 Bean(如 @Configuration 类中的 @Bean 方法)、条件化加载 Bean(如 @Conditional 的实现)、AOP 代理增强(如 @EnableAspectJAutoProxy 的底层支持)等
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.stereotype.Component;
@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
// 修改或检查 BeanDefinition
System.out.println("正在处理 BeanFactory...");
}
}
2
3
4
5
6
7
8
9
10
11
12
# initApplicationEventMulticaster
此方法初始化和管理各种事件,ApplicationEventMulticaster 是 Spring 事件机制的核心组件,负责注册事件监听器(ApplicationListener):存储所有监听特定事件的监听器,并且广播事件(publishEvent):当事件发布时,通知所有匹配的监听器
事件需要用户自定义,当然 spring 容器也内置了一些自己的事件,比如:
- ContextRefreshedEvent:容器初始化完成(refresh() 方法结束时)
- ContextStartedEvent 调用:start() 方法时
- ContextStoppedEvent:调用 stop() 方法时
- ContextClosedEvent:容器关闭时
如果需要接受事件,可以实现以下方法(以接受容器初始化完成事件为例):
@Component
public class MyContextListener {
@EventListener
public void handleContextRefresh(ContextRefreshedEvent event) {
System.out.println("Spring容器初始化完成");
}
@EventListener
public void handleContextClose(ContextClosedEvent event) {
System.out.println("Spring容器关闭");
}
}
2
3
4
5
6
7
8
9
10
11
12
如果想在 spring 容器启动完毕后执行一段逻辑,除了可以监听 spring 初始化完成的事件,还可以实现 ApplicationRunner 接口,这个接口是 spring 对事件的封装
# bean 创建
需要关注的点是:两次循环依赖、作用域判断、对象创建、实例化时 CGlib 处理,并且有很多步骤都是认证这个 bean 是否合法
# getBean
在以上这些结束后,Spring 遍历 map 集合,将所有的 bean 初始化,初始化的过程封装到了 getBean 方法里
getBean 应该是我们获取 bean 使用的方法(你也可以使用这个方法来获取对象),所以这个方法有一层判断,如果在单例池中存在这个 bean,直接返回这个 bean
final String beanName = transformedBeanName(name);
Object bean;
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
......
bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
}
2
3
4
5
6
7
接下来是一大串 bean 认证环节(认证这个 bean 是否合法)
在认证的过程中有一步,需要先初始化依赖的 bean,此时初始化的是 depends-on 中定义的依赖,而不是它调用的依赖,因此必定不支持循环依赖
以下过程,判断该对象依赖是否为空,如果有依赖,使用 isDependent 方法判断是否循环依赖,如果是,抛异常
String[] dependsOn = mbd.getDependsOn();
if (dependsOn != null) {
for (String dep : dependsOn) {
if (isDependent(beanName, dep)) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
}
registerDependentBean(dep, beanName);
getBean(dep);
}
}
2
3
4
5
6
7
8
9
10
11
然后进行 bean 作用域的判断,不管这个 bean 的作用域是什么(单例原型其他),总是要进入 createBean 方法中,createBean 进行一些认证又会转入 doCreateBean 中
实例化 bean
instanceWrapper = createBeanInstance(beanName, mbd, args);
进入此方法,又是一段认证过程,然后实例化时分两种情况,存在方法覆写时使用 CGLIB 代理返回一个代理对象,没有就使用 java 反射直接创建一个 bean 对象实例(之间可能还会使用有参构造创建对象或者无参构造创建对象)
随后会进入 DI 的流程,在依赖注入的时候,如果发现需要注入的对象尚未初始化,还需要触发注入对象的初始化动作,同时在注入的时候也会分为按名称注入和按类型注入(除此之外还有构造器注入等方式)
# 循环依赖
循环依赖指的是当我们有两个类 A 和 B,其中 A 依赖 B,B 又依赖了 A,或者多个类也一样,只要形成了一个环状依赖那就属于循环依赖
如果换成我们来开发,我们会如何解决这个问题呢?其实方法也很简单,大家应该都能想到,那就是当我们把对象初始化之后,在没有注入属性之前,就先缓存起来,这样,就相当于缓存了一个半成品 Bean 来提前暴露出来供注入时使用,这也是 Spring 处理循环依赖的方法
但是以下三种情况无法解决循环依赖问题:
- 只有 set 注入才能解循环依赖,构造器注入不行,因为 set 在对象被实例化之后才进行 DI,在实例化与 DI 之间有操作空间,如果在构造器是解单例的循环依赖可能就要在 JVM 上操作操作了,构造器注入代码如下:
@Controller
public class FooController {
private final FooService fooService;
@Autowired
public FooController(FooService fooService) {
this.fooService = fooService;
}
}
2
3
4
5
6
7
8
9
10
- 非单例模式 Bean,因为只有在单例模式下才会对 Bean 进行缓存。如果是原型模式,会走到 AbstractBeanFactory 类中下面的判断,抛出异常。原因很好理解,创建新的 A 时,发现要注入原型字段 B,又创建新的 B 发现要注入原型字段 A。这就套娃了,你猜是先 StackOverflow 还是 OutOfMemory?Spring 怕你不好猜,就先抛出了 BeanCurrentlyInCreationException
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
2
3
- 手动设置了 allowCircularReferences=false,则表示不允许循环依赖
来看看 Spring 是怎么处理的,容器会创建三级缓存,表示此 bean 是否正在创建,这些缓存主要用来解决循环依赖。这就是面试中常常被提到的 Spring 如何处理循环依赖的问题
- 一级缓存:Map<String,Object> singletonObjects,单例池,用于保存实例化、属性赋值(注入)、初始化完成的 bean 实例。这里面的 bean 已经完成了 DI,项目启动完成后获取 bean 实例时从此获取,因此这其实不是缓存而是 IOC 容器
- 二级缓存:Map<String,Object> earlySingletonObjects,早期曝光对象,用于保存已经实例化、已经完成 DI 或者尚未完成 DI、一定没有执行初始化回调的 Bean
- 三级缓存:Map<String,ObjectFactory<?>> singletonFactories,早期曝光对象工厂,用于保存 bean 创建工厂,bean 创建工厂则是用于延迟获取 Bean,只有在真正调用 getObject() 时才会执行实际的 Bean 创建逻辑
刨除掉所有的异常情况,进入判断这个 bean 是否允许循环依赖的代码,如下:
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
if (logger.isDebugEnabled()) {
logger.debug("Eagerly caching bean '" + beanName +
"' to allow for resolving potential circular references");
}
addSingletonFactory(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
return getEarlyBeanReference(beanName, mbd, bean);
}
});
}
2
3
4
5
6
7
8
9
10
11
12
13
14
具体判断循环依赖的逻辑是这样的:
1,创建 A 实例,实例化的时候把 A 对象工厂放⼊三级缓存,表示 A 开始实例化了,虽然我这个对象还不完整,但是先曝光出来让大家知道。这时候曝光的对象仅仅是构造完成,还没注入属性和初始化,提前曝光的对象被放入 Map<String, ObjectFactory<?>> singletonFactories 缓存中,这里并不是直接将 Bean 放入缓存,而是包装成 ObjectFactory 对象再放入
2,A 注⼊属性时,发现依赖 B,此时 B 还没有被创建出来,所以去实例化 B
3,同样,B 注⼊属性时发现依赖 A,它就会从缓存里找 A 对象。依次从⼀级到三级缓存查询 A,从三级缓存通过对象⼯⼚拿到 A,发现 A 虽然不太完善,但是存在,此时构建 A,把 A 放⼊⼆级缓存,同时删除三级缓存中的 A,B 已经实例化并且初始化完成,把 B 放入⼀级缓存
4,接着 A 继续属性赋值,顺利从⼀级缓存拿到实例化且初始化完成的 B 对象,A 对象创建也完成,删除⼆级缓存中的 A,同时把 A 放⼊⼀级缓存
那么问题来了,为什么要三级缓存,为什么需要包装?如果只实现这个功能的话二级缓存不是够了吗?二级缓存中存放的,就是未完成的 bean 啊。这么做的好处是什么?二级缓存绰绰有余不是吗
其实三级缓存中放的是生成具体对象的匿名内部类,在一个 bean 被 AOP 的时候,它才会⽣成代理对象,没有的话就会返回普通对象。Spring 不提前创建好对象,在出现循环依赖被其他对象注入时,才会根据三级缓存实时生成代理对象或者非代理对象
没有三级缓存的情况(二级缓存方案),会强制提前创建代理,在 Bean 实例化后,尚未初始化时,就必须立即创建代理对象放入二级缓存,而代理对象的创建是需要使用后置处理器 AnnotationAwareAspectJAutoProxyCreator 的,因为 bean 的生命周期中,依赖注入在 bean 初始化之前,如果采用二级缓存的方案,bean 初始化和依赖注入就一起完成了
但是用了三级缓存,就可以先把要生成的类记录下来,等到需要生成该类或者循环依赖发生时才实例化该类,用这种惰性加载的方式最大程度的不打破 bean 的生命周期,同时由于 bean 还没有完成属性注入,某些 AOP 逻辑(比如基于属性的切面匹配)可能无法确定,但是如果出现了循环依赖,还是会打破 bean 生命周期的
如此,处理完循环依赖后,Spring 继续设值,DI。DI 完成之后,这还不是一个完整的 bean,它还需要进行初始化 initializeBean,也就是 bean 生命周期的一部分。Spring 处理问题的思路,简单来说就是缓存,更简单一些,是使用 map,将创建过的对象都保存起来。这也就是为什么有人说,处理该问题的思路是一道简单的算法题
但是,循环依赖的问题还没有结束,就算 Spring 已经处理了循环依赖,在平时的代码中还是可能会出现循环依赖问题(如果设置了不允许支持循环依赖的话)
Error creating bean with name 'trackInfoServiceImpl': Injection of resource dependencies failed;
nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Errorcreating bean with name
'salesClueService': Requested bean is currently in creation: Is there an unresolvable circular reference?
2
3
这种情况一般出现在系统设计的不好,Service 没有做分层,导致同级业务之间相互调用的问题,这种时候我们一般在方法中使用 getBean 在方法中强行获取某个 service 对象
# 工厂 bean
Spring 有两种 bean,一种是普通 bean,另外一种是工厂 bean,我们在项目中一般使用普通 bean 就绰绰有余了,但是工厂 bean 可以用来构建更加复杂的对象,比如你想在 bean 初始化的时候执行一段复杂的逻辑(如读取配置、设置缓存等),可以使用工厂 bean 来实现
普通 bean 调用 getBean 方法的时候会返回这个 bean 的对象,而这种工厂 bean 的特性就是在调用 getBean 时不一定返回该 bean 对象,甚至返回值都不一定是该类类型,其返回值是该 FactoryBean 的 getObject 方法所返回的对象。创建出来的对象是否属于单例由 isSingleton 中的返回决定
你可以实现它以重写 FactoryBean
public class FactoryBeanImpl implements FactoryBean {
@Override
public Object getObject() throws Exception {
......
}
@Override
public Class<?> getObjectType() {
......
}
}
2
3
4
5
6
7
8
9
10
那这种 bean 有什么好处呢,它可以基于工厂设计模式创建对象。在某些情况下,实例化 Bean 过程比较复杂,如果按照传统的方式,则需要在 bean 中提供大量的配置信息。配置方式的灵活性是受限的,这时采用编码的方式可能会得到一个简单的方案。Spring 为此提供了一个 org.springframework.bean.factory.FactoryBean 的工厂类接口,用户可以通过实现该接口定制实例化 Bean 的逻辑
# bean 的生命周期
就是 bean 在它生成、使用、销毁过程中可能会发生什么事情:
1,实例化 (Instantiation):通过构造函数或工厂方法创建 Bean 实例。此时对象处于原始状态,属性未注入,这个 bean 还是一个对象,执行了对象的 init 方法、构造方法
2,属性赋值 (Population of Properties):设置 Bean 的属性值和依赖(通过 setter 或字段注入),此时处理 @Autowired、@Value 等注解发挥作用
3,Bean 初始化,这一步会做很多操作,主要是实现 spring 为 bean 提供的一些额外处理
- Aware 接口回调(Aware Interface Callbacks):Aware 接口是一组标记接口,在 initializeBean 方法中会被调用
public class MyBean implements BeanNameAware, BeanFactoryAware, ApplicationContextAware {
@Override
public void setBeanName(String name) {
System.out.println("3.1 Aware回调 - BeanName: " + name);
}
@Override
public void setBeanFactory(BeanFactory beanFactory) {
System.out.println("3.2 Aware回调 - BeanFactory");
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
System.out.println("3.3 Aware回调 - ApplicationContext");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- 初始化前处理 (Pre-Initialization):调用 BeanPostProcessor.postProcessBeforeInitialization(),这就是上面的 bean 处理器需要注册的内容
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
if(bean instanceof MyBean) {
System.out.println("4,BeanPostProcessor前置处理 - BeforeInitialization: " + beanName);
}
return bean;
}
}
2
3
4
5
6
7
8
9
10
- 初始化 (Initialization):有多种初始化处理方式,比如调用自定义的 init-method ,用 XML 配置或 @Bean(initMethod="...")) 方式实现、执行 @PostConstruct 注解的方法等
public class MyBean implements InitializationBean {
@PostConstruct
public void initAnnotation() {
System.out.println("5.1 初始化 - @PostConstruct方法");
}
@Override
public void afterPropertiesSet() {
System.out.println("5.2 初始化 - InitializingBean.afterPropertiesSet()");
}
public void initMethod() {
System.out.println("5.3 初始化 - init-method");
}
}
// 配置类中指定init-method,初始化时就会调用
@Configuration
public class AppConfig {
@Bean(initMethod = "initMethod")
public MyBean myBean() {
return new MyBean();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
- 初始化后处理 (Post-Initialization):调用 BeanPostProcessor.postProcessAfterInitialization()
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
if(bean instanceof MyBean) {
System.out.println("6. BeanPostProcessor后置处理 - AfterInitialization: " + beanName);
}
return bean;
}
}
2
3
4
5
6
7
8
9
10
4,销毁 (Destruction):和初始化一样,也有多种实现方式
实现 DisposableBean 接口的 destroy() 方法,调用自定义的 destroy-method、调用 @PreDestroy 注解的方法等
public class MyBean implements DisposableBean {
@PreDestroy
public void preDestroy() {
System.out.println("8.1 销毁 - @PreDestroy方法");
}
@Override
public void destroy() {
System.out.println("8.2 销毁 - DisposableBean.destroy()");
}
public void destroyMethod() {
System.out.println("8.3 销毁 - destroy-method");
}
}
// 配置类中指定destroy-method
@Configuration
public class AppConfig {
@Bean(destroyMethod = "destroyMethod")
public MyBean myBean() {
return new MyBean();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 使用 IOC
# bean 的作用域
bean 的作用域用于确定哪种类型的 bean 实例应该从 Spring 容器中返回给调用者,从生成方式上来讲只有单例、原型和其他,因为源码是这么写的
1,singleton:单例 bean,获取的每一个 bean 地址都相同,spring 默认使用这个生成方式,当创建起容器时就同时自动创建了一个 bean 的对象,不管你是否使用,他都存在了
2,prototype:原型 bean,每一次使用都会生成一个新对象,创建容器的时候并没有实例化
3,request:每一次 http 请求都会生成一个新对象,这个 bean 只作用在 http 的 request 中
4,session:每一次 http 请求都会生成一个新对象,这个 bean 只作用在 http 的 session 中。会话和请求的区别是,会话指客户端与服务器之间的一系列交互组成的逻辑关联,包含多个请求和响应周期,服务器使用会话机制(如Session ID)来跟踪用户状态
你可以使用 @Scope 来声明作用域,scope 是范围的意思,比如
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
除了单例模式其他都不怎么会使用到
# 如何定义一个 bean
@Component:通用的 bean 定义,如果不知道是哪一层的 bean 就使用这个
@Repository:仓库的意思,对应持久层 dao 层的 bean
@Service:服务层,主要涉及一些复杂的逻辑
@Controller:对应 Spring MVC 控制层,主要用户接受用户请求并调用 Service 层返回数据给前端页面,这里除了定义为 bean 以外,还会将类中方法注册到默认 controller 中,处理 HTTP 请求,并返回视图或响应数据
@Bean:将一个方法的返回值标记为 bean,是对方法使用的注解(可以在没有源码的情况下将第三方代码中的类标记为组件,这种情况下使用 component 就不能解决这种需求了)
@Mapper:设置一个 bean,对应持久层 dao 的注解,同时该 bean 还可以被 mybatis 扫描到
# 如何使用 bean
使用 Autowired 或者 Resource 来进行 DI,Autowired 根据类型注入,是 spring 自带的注解,Resource 根据名称优先注入,来源于 jsr-250,如果找不到对应的名称会按照类型来注入。因此 resource 更优秀一些(如果根据类型来可能会返回多个 bean)
spring 可以注入 List 或者 Map,支持这种基于接口实现类的直接注入,比如如下示例
@Autowired
private List<BeanInterface> list;
@Autowired
private Map<String, BeanInterface> map;
2
3
4
5
接口的实现可以使用遍历来让目标数据执行每个实现类的方法
hotelListProcessors.forEach(item -> item.process(context));
Spring 把 bean 放入了 List 中 那个这个顺序怎么控制呢?可以在实现类中加入 @Order(value) 注解即可 ,值越小越先被初始化越先被放入 List
# @Autowired 与 @Resource 的区别
Autowired 属于 Spring 内置的注解,默认的注入方式是根据类型进行匹配,通常被称为 byType。当它通过类型找到多个对象的时候,可以配合 Qualifier 注解来实现按照名称的依赖注入
@Autowired
@Qualifier("mainDataSource")
private DataSource dataSource;
2
3
还找不到的话,那就只能报错了
对于 @Autowired 声明的数组、集合类型,spring 并不是根据 beanName 去找容器中对应的 bean,而是把容器中所有类型与集合(数组)中元素类型相同的 bean 构造出一个对应集合,注入到目标 bean 中
Resource 属于 JDK 提供的注解,默认注入方式为 byName。如果无法通过名称匹配到对应的 Bean 的话,注入方式会变为 byType。同时你也可以在该注解直接指定 bean 名称