在上文的启动流程探索中,我们知道ioc跟bean的创建是在SpringApplication 对象构造好后,执行run()
方法时,再具体一点就是refreshContext()
方法执行时
java
// 创建`IOC`容器
context = this.createApplicationContext();
// 设置一个启动器,设置应用程序启动
context.setApplicationStartup(this.applicationStartup);
// 准备IOC容器的基本信息
this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
// 刷新IOC容器
this.refreshContext(context);
// 执行刷新后的处理
this.afterRefresh(context, applicationArguments);
那Bean执行过程中经历了哪些,它们之间的循环依赖又是什么?
Bean的创建过程
一个bean
的创建大概分为4个步骤:
- createInstance------调用构造函数,创建实例
- 注入依赖,property赋值
- 回调Aware、postProcessor..
- 放入缓存
循环依赖
那所谓的循环依赖是什么意思呢?
是指在创建Bean的第二阶段------注入依赖
时,发现该Bean依赖另一个Bean,而另一个Bean的创建又依赖前一个Bean。从而导致彼此都无法创建的情况。
java
@Service
public class AService {
@Autowired
private BService bService;
}
java
@Service
public class BService {
@Autowired
private AService aService;
}
以上代码就是一种典型的情况,当然循环依赖也不只发生在两个Bean之间,多个Bean也可能发生,即使是单个Bean也可能发生------------自己需要自己。
java
@Service
public class CService {
@Autowired
private CService cService;
}
缓存结构
对于循环依赖,Spring之所以能解决是因为独特的缓存结构。Spring中的缓存一共有三层:
java
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 一级缓存
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
// 二级缓存
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
// 三级缓存
一级缓存:用来存放完全初始化好的Bean
,在创建过程中的最后一步,就是放入的这一层。
二级缓存:存放的是半成品的Bean
,就是尚未注入依赖的Bean,未进行一些必要的初始化
三级缓存:这里存放的则是BeanFactory
(创建Bean的工厂),调用其中的方法getObject()
会返回半成品的Bean
,之后该半成品的Bean会被放入二级缓存中 。在Bean创建的第一阶段
完成后,该工厂就会被放入三级缓存中。
如果该过程中Bean的创建顺利,没有出现循环依赖,那么在Bean被创建完成后,在放入一级缓存中后,会将三级缓存中的对象工厂给删除。
使用缓存解决循环依赖
清楚了缓存结构,那么接下来看看它是如何解决bean创建过程中的循环依赖的。就用上面的AService跟BService例子
java
@Service
public class AService {
@Autowired
private BService bService;
}
- 调用
AService
的构造函数进行对象的创建,完成后会放一个ObjectFactory到三级缓存 - 之后进行注入依赖(给各个属性赋值),这时发现它依赖BService
- 接着去一级缓存 中找是否有BService(之后继续在其他缓存中找),发现没有,于是就会进入BService的创建过程
BService
在对象创建后,同样会放一个它的ObjectFactory到三级缓存中- 接着进行
BService
的依赖注入,发现需要AService,于是进入缓存中找,最终在三级缓存中找到了AService
的ObjectFactory
,于是调用其getObject方法,得到一个AService的半成品Bean,并将其放入二级缓存中,同时清除三级缓存中的A - 因为
AService
的依赖已经找到,于是BService
的Bean创建成功,因为它创建完成了,此时会将BService
的bean放到一级缓存中 - 此时
BService
的bean已经有了,继续AService
的bean创建,所有依赖都有了,于是最终AService
的bean也会创建成功
为什么需要三级缓存、二级缓存
看了上面的流程,好像只用二级缓存也能解决循环依赖问题,因为只需要一个存放ObjectFactory
的缓存就可以解决循环依赖问题,或者说即使只有一级缓存也能解决循环依赖问题。为什么要设计三级缓存呢?
这是因为最终存放在一级缓存中的bean可能不是第一步中创建的原始实例,有可能是AOP中的代理对象,比如以下这种情况
java
@Service
public class AService{
@Transactional
public void test(){
}
}
因为加了@Transactional
所以在对象工厂中生成的就是代理对象,而非第一步中生成的原始对象。所以需要三级缓存,这也是三级缓存
存在的主要原因。
那如果把ObjectFactory
放入二级缓存呢?放到三级。这是为了延迟加载,因为不是每个bean的生成会导致循环依赖,Spring它考虑的是只有在循环依赖发生的时候,才缓存半成品bean。
同时也是为了提高效率,比如以下这种情况
java
@Service
public class AService{
@Autowired
private BService bService;
}
@Service
public class BService{
@Autowired
private AService aService;
@Autowired
private CService cService;
}
@Service
public class CService{
@AutowireA
private AService aService;
}
- 先创建A发现需要B
- 接着创建B,发现需要A,于是创建A对象工厂并放入三级,并生成半成品A放入二级
- 继续注入依赖C,发现没有C
- 在创建C的过程中,发现它也依赖A,这个时候之前存的二级缓存就起到了作用,可以直接创建
总结一下:
- 一级缓存则是存放的是实例化完成的bean
- 二级缓存中的bean有可能是半成品对象,也有可能是代理半成品对象。
- 三级缓存存的是ObjectFactory,主要是为了AOP的实现
依赖注入扩展
@Autowired 和 @Resource 有什么区别
@Autowired 和 @Resource 都是 Spring/Spring Boot 项目中,用来进行依赖注入的注解。它们都提供了将依赖对象注入到当前bean的功能
-
来源不同:@Autowired 是 Spring 定义的注解,而 @Resource 是 Java 定义的注解
-
查找顺序不同:
- @Autowired 是先根据类型(byType)查找,如果存在多个 Bean 再根据名称(byName)进行查找
- @Resource 是先根据名称查找,如果(根据名称)查找不到,再根据类型进行查找
-
支持的参数不同:@Autowired 只支持设置 1 个参数,而 @Resource 支持设置 7 个参数
-
依赖注入的支持不同
- @Autowired 既支持
构造方法注入
,又支持属性注入
和Setter注入
- @Resource 只支持
属性注入
和Setter注入
- @Autowired 既支持
指定构造方法
bean的第一步是对象的构造,spring中bean的默认创建是调用无参构造方法
。
当然你可以指定构造方法。大家可以用一下简单代码进行实验:
java
@Service
public class AService {
private UserService userService;
public AService() {
System.out.println("bean Aservice 无参构造");
}
@Autowired
public AService(UserService userService) {
System.out.println("执行有参构造");
this.userService = userService;
}
}
以上代码就是通过@Autowired
来指定使用有参构造来创建对象。项目启动时,在控制台便会打印有参构造对应的语句,而不会执行无参构造。
注解@PostConstruct
完成依赖注入后,就进入了第三阶段,执行各种回调。在依赖注入完成后,你如果想对bean做一些操作,比如缓存一些值,或者其他。你可以通过@PostConstruct
注解来实现:
java
@Service
public class AService {
private UserService userService;
public AService() {
System.out.println("bean Aservice 无参构造");
}
@PostConstruct
public void Test(){
System.out.println("PostConstruct");
}
@Autowired
public AService(UserService userService) {
System.out.println("执行有参构造");
this.userService = userService;
}
}
使用该注解的方法会在依赖注入
完成后被自动调用。它的作用是:对当前bean进行一些初始化操作。调用顺序如下:
- Constructor >> @Autowired >> @PostConstruct
InitializingBean接口
除了通过注解的方式可以实现:自定义方法,在依赖注入
完成后自动调用外,还可以用实现接口的方式。在bean类中实现接口InitializingBean
,并重写afterPropertiesSet()
方法:
java
@Service
public class AService implements InitializingBean {
private UserService userService;
public AService() {
System.out.println("bean Aservice 无参构造");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("initializing ---");
}
}
此时在AService
完成依赖注入后,便会执行afterPropertiesSet
方法
注意
Spring Boot 2.6.0之后,如果程序中存在循环依赖问题,启动上就会失败,报错
如果要启动,需要进行一些设置
properties
spring.main.allow-circular-references = true