问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

Spring依赖注入、循环依赖、三级缓存详解

创作时间:
作者:
@小白创作中心

Spring依赖注入、循环依赖、三级缓存详解

引用
1
来源
1.
https://www.cnblogs.com/yang37/p/18249629

本文详细介绍了Spring框架中的依赖注入机制、循环依赖问题及其解决方案。通过代码示例和原理分析,帮助读者深入理解Spring框架的核心概念。

1. 依赖注入

1.1 依赖注入的方式

方式
优点
缺点
适用场景
属性注入
简洁,减少样板代码,直接在字段上注入依赖。
不利于测试,无法使用final字段,可能导致类的职责不清晰。
适用于简单的依赖注入,测试和重构要求不高的场景。
Setter 注入
灵活,易于理解和维护,支持可选依赖注入,Setter方法可以包含验证逻辑。
需要额外的Setter方法,可能导致代码冗长,依赖不能为final字段。
适用于依赖可选且可能变化的场景,需要在注入前进行额外处理。
构造方法注入
强制依赖注入,保证依赖完整性,支持final字段,有助于单元测试。
需要额外的构造函数,可能在有多种依赖时显得冗长。
适用于依赖项多且不可或缺的场景,需要进行严格依赖管理和单元测试。

上面3个方式,分别对应将@Autowired注解用在属性上、set方法上、构造方法上。

为了方便演示,我们修改之前的工程,添加一个实体类User,并在MyConfig中注册为Bean。然后我们用一个UserService来测试这三种注入方式。

1.1.1 属性注入

属性注入很简单,就是我们最常用的那样,直接在属性上使用@Autowired

@Slf4j
@Service
public class UserService {
    @Autowired
    private User user;
    void showInfo() {
        log.info("user: {}", JSON.toJSONString(user));
    }
}

1.1.2 Setter方法注入

setter注入,就是把注解用在set方法上。

@Slf4j
@Service
public class UserService {
    private User user;
    @Autowired
    public void setUser(User user) {
        this.user = user;
    }
    void showInfo() {
        log.info("user: {}", JSON.toJSONString(user));
    }
}

1.1.3 构造方法注入

@Slf4j
@Service
public class UserService {
    private User user;
    @Autowired
    public UserService(User user) {
        this.user = user;
    }
    void showInfo() {
        log.info("user: {}", JSON.toJSONString(user));
    }
}

1.2 依赖注入原理

现在,我们来研究下UserService中的user对象是如何被注入的。还记得前面提到的,我们反射创建Bean后,会根据Bean中的属性来进行注入。直接debug到之前提到的:

// 实例化所有剩余的(非lazy-init)单例。
finishBeanFactoryInitialization(beanFactory);

位于org/springframework/context/support/AbstractApplicationContext.java。F7直接步入进去,看如何创建。嗯,上一步的入参是beanFactory,这里直接调用beanFactory的方法。继续进去,来到了preInstantiateSingletons的方法内部,哎?this是谁,this不就是咱们beanFactory自己吗。右键加上条件断点,注意啊,我是加在for里面的那行,条件断点不就是让代码执行到满足条件的情况嘛。然后下面的if又判断是不是FactoryBean,我们不是,所以,咱直接关掉这个if,不就到了创建了嘛。之后的过程就比较复杂了,会走到org/springframework/beans/factory/support/AbstractAutowireCapableBeanFactory.java的populateBean方法中。

把条件断点打成:

bp.getClass().getName().contains("AutowiredAnnotationBeanPostProcessor")

这里,就是根据不同的bp(BeanPostProcessor)来执行对应的postProcessProperties方法。比如,我们此处条件断点到了。而我们的org/springframework/beans/factory/annotation/AutowiredAnnotationBeanPostProcessor.java就是在注入Autowired的属性。经过这个方法后,其中的user对象已经能够被注入了。

那么这个AutowiredAnnotationBeanPostProcessor.java中具体的注入逻辑是啥呢。没事,我们直接把干扰的代码去掉。

@Override
protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
    Field field = (Field) this.member;
![](https://wy-static.wenxiaobai.com/chat-rag-image/4052932685478306118)
    // 查找值
    Object value = this.cached ? resolvedCachedArgument(beanName, this.cachedFieldValue) : resolveFieldValue(field, bean, beanName);
    
    // 注入值
    ReflectionUtils.makeAccessible(field);
![](https://wy-static.wenxiaobai.com/chat-rag-image/9662442621790026144)
    field.set(bean, value);
}

好,那注入的逻辑就是BeanPostProcessor的不同,比如我们换成通用注解@Resource,那这里的条件断点就修改成这样。

1.3 @Autowired和@Resource

项目
@Resource
@Autowired
出处
Java注解 (javax.annotation.Resource)
Spring注解 (org.springframework.beans.factory.annotation.Autowired)
默认自动装配策略
名称优先
类型优先
名称自动装配
支持
通过@Qualifier支持
类型自动装配
名称未找到时按类型
默认
可空性
不适用
可以为null(required=false)
类级别注解
支持
不适用

2. 循环依赖

2.1 是什么

循环依赖问题,讲的就是两个Bean对象互相依赖,你中有我,我中有你。例如下方的UserService其中需要注入我们的PasswordService,而PasswordService呢又需要一个UserService。

  • 要创建UserService,你得把PasswordService先给我呀。
  • 要创建PasswordService,你得把UserService先给我呀。
    ???
@Slf4j
@Service
public class UserService {
    @Resource
    private PasswordService passwordService;
}
@Service
public class PasswordService {
    @Resource
    private UserService userService;
}

碰到这种场景,启动的时候会给你明显的报错信息,告诉你有循环依赖的问题。当然咯,以下这些场景都是类似的。

2.1.1 自己依赖自己

2.1.2 双方互相依赖

2.1.3 多方互相依赖

2.2 解决方案

2.2.1 优化代码

好,那么什么场景下可能导致这个问题,以上方的UserService和PasswordService为例。那他们既然在内部注入了,肯定就是:

  • UserService中想用PasswordService
  • PasswordService中想用UserService

最简单的方式,就是抽离出第三方类,聚合这两个玩意,让它们不要在内部互相依赖。

@Service
public class CommonService {
    @Resource
![](https://wy-static.wenxiaobai.com/chat-rag-image/12342885826317339745)
    private UserService userService;
![](https://wy-static.wenxiaobai.com/chat-rag-image/7725819475559892565)
    @Resource
    private PasswordService passwordService;
}

然后将我们涉及到依赖的逻辑抽离到这个CommonService中,用CommonService来完成操作。

2.2.2 三级缓存

2.2.2.1 回顾Bean的创建

  1. 普通的Bean
    首先呢,我们看下Bean是怎么获取的,从我们的BeanFactory的单例池中来拿到。单例池中没有,我们就会尝试创建这个Bean。下方演示一个简单的bean对象创建,其中包含一个属性id。
  • 获取Bean:getBean(a) 获取这个bean(发现没有)
  • 实例化:没有,则反射创建这个Bean实例。
  • 填充属性:得到Bean实例后,通过populateBean()来填充其中的属性id。
  • 初始化:最后执行下我们的初始化方法,例如之前提到的postProcessAfterInitialization(在bean初始化之前执行)的一些初始化方法。
  • 放到单例池中去
  1. 属性中包含别的对象的bean
    那么假设对象A中还有个对象B呢?无非就是填充属性的时候,把这个B创建出来。

  2. 属性产生循环依赖的bean
    当B对象中需要A对象时,创建则会产生死循环。

  • a对象中需要依赖b对象,则开始b对象的创建。
  • b对象中执行属性填充的时候,尝试获取a对象。
  • 由于此时a对象也还没创建出来(单例池中没有a),那么又会触发a对象的创建。
  • 死循环

2.2.2.2 三级缓存的引入

回顾上方的内容,我们发现问题在哪。问题在于,填充属性的时候,getBean在单例池中没有对象可以拿,没法填充了。

所以,基本的思路就是,反射实例化后,我们先把这些对象放到半成品的池子里(没有填充对象,半成品)。在单例池中拿不到时,尝试从半成品池子中拿。先看b填充a属性的那里,虽然单例池拿不到a,但是我们可以从半成品池子中拿到a,进而走完Bean对象b的创建。拿到半成品的a后,完成b的创建,最后b被放到了单例池中去。最后,对象a在填充属性b的时候,单例池中已经有b能填充了,a对象也能完成创建。创建完成a后,检查下把无意义的半成品池中的a删除掉即可。至此,a和b都拿到了实际的对象(非半成品对象),创建完成。

哎,看起来2级缓存已经够用了,为啥spring最后搞的是三级缓存?因为咱们Spring中创建出来的不是普通对象,最后Spring要交付的是经过AOP代理的对象。即,当对象a中注入属性b的时候,注入的可不能是仅仅简单实例化的半成品b,而是要注入代理对象proxy$b。

半成品池子中,存放的是未属性填充的半成品对象,不是AOP对象。如果是aop代理,经过填充后,b里面的a是不对的。那么这个代理对象是在啥时候创建的?在初始化阶段通过我们的postProcessAfterInitialization来创建的。这个PostProcessor就是AspectJAwareAdvisorAutoProxyCreator。

AspectJAwareAdvisorAutoProxyCreator中有两个重要的方法。

  • postProcessBeforeInstantiation决定是否要为当前 Bean 创建一个代理对象。如果需要代理,就返回代理对象;否则返回null,继续正常的 Bean 实例化过程。
  • getEarlyBeanReference返回一个可能的代理对象,确保在循环依赖情况下,其他 Bean 获取到的是这个 Bean 的代理对象而不是原始对象。

所以,现在加上AOP后,实际上应该是这个样子。以对象a为例

  • 在初始化时,执行a的后置处理器创建代理对象proxy$A。
  • 将proxy$A放回单例池

回到循环依赖的问题,以b填充a属性为例,关键点是我们属性填充的时候,填不出来一个动态代理的proxy$A啊。要是可以把初始化阶段的后置处理提前就好了实际上就是这样做的,我们把创建代理动态代理的步骤提前,打包成一个工厂方法,故而产生了我们的第3个池子,工厂池。我们调用这个工厂池中跟这个对象绑定的工厂方法,从而提前的来创建出这个动态代理。所以,我们就能把半成品池子中的对象替换成最后需要的代理对象。经过上面的操作后,我们单例池中就能存放到实际的代理对象,解决掉我们放不进去代理对象的问题。

最后回顾下整个过程中的三个缓存池。

  • singletonObjects: 单例bean缓存(完整的Bean对象)
  • earlySingletonObjects: 早期单例bean缓存(半成品对象)
  • singletonFactories: 单例bean工厂缓存(用于创建代理对象的工厂对象)
© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号