[杂谈-2]使用三级缓存避免循环依赖问题

0 阅读8分钟

1.循环依赖问题

a.依赖分组

图源:小傅哥

img

  • 循环依赖主要分为这三种,自身依赖于自身、互相循环依赖、多组循环依赖。
  • 但无论循环依赖的数量有多少,循环依赖的本质是一样的。就是你的完整创建依赖于我,而我的完整创建也依赖于你,但我们互相没法解耦,最终导致依赖创建失败。
  • 所以需要 Spring 提供了除了构造函数注入和原型注入外的,setter 循环依赖注入解决方案

b.使用一级缓存粗糙地解决问题

img

  • 如果仅以一级缓存解决循环依赖,那么在实现上可以通过在A对象 newInstance 创建且未填充属性后,直接放入缓存中。
  • A对象的属性填充B对象时,如果缓存中不能获取到B对象,则开始创建B对象,同样创建完成后,把B对象填充到缓存中去。
  • 接下来就开始对B对象的属性进行填充,恰好这会可以从缓存中拿到半成品的A对象,那么这个时候B对象的属性就填充完了。
  • 最后返回来继续完成A对象的属性填充,把实例化后并填充了属性的B对象赋值给A对象的b属性,这样就完成了一个循环依赖操作。
// 定义一个线程安全的存储单例对象的 Map
private final static Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
​
// 泛型方法,用于获取指定类的 Bean 实例
private static <T> T getBean(Class<T> beanClass) throws Exception {
    // 获取 Bean 的名称(类名的小写形式)
    String beanName = beanClass.getSimpleName().toLowerCase();
    
    // 检查 Map 中是否已经存在该 Bean 的实例
    if (singletonObjects.containsKey(beanName)) {
        // 如果存在,则直接返回该实例
        return (T) singletonObjects.get(beanName);
    }
    
    // 如果不存在,则创建一个新的 Bean 实例
    Object obj = beanClass.newInstance();
    
    // 将新创建的 Bean 实例放入缓存中
    //创建Bean马上放入Map中,不等待填充就放入.
    singletonObjects.put(beanName, obj);
    
    // 获取该对象的所有声明字段(属性)
    Field[] fields = obj.getClass().getDeclaredFields();
    
    // 遍历每个字段进行依赖注入
    //我们是先:将实例放入Map中,再进行注入。
    for (Field field : fields) {
        // 设置字段可访问,即使它是私有的
        field.setAccessible(true);
        
        // 获取字段的类型
        Class<?> fieldClass = field.getType();
        
        // 为字段类型生成对应的 Bean 名称(小写形式)
        String fieldBeanName = fieldClass.getSimpleName().toLowerCase();
        
        // 判断 singletonObjects 中是否已有此字段类型的 Bean 实例
        field.set(obj, singletonObjects.containsKey(fieldBeanName) 
                      ? singletonObjects.get(fieldBeanName) // 如果有,则直接注入
                      : getBean(fieldClass)); // 如果没有,则递归调用 getBean 方法创建并注入
        
        // 还原字段的可访问性
        field.setAccessible(false);
    }
    
    // 返回创建好的 Bean 实例
    return (T) obj;
}
​
public static void main(String[] args) throws Exception {
    System.out.println(getBean(B.class).getA());
    System.out.println(getBean(A.class).getB());
}
​
cn.ceshi.springframework.test.A@49476842
cn.ceshi.springframework.test.B@78308db1
​
Process finished with exit code 0
  • 从测试效果和截图依赖过程中可以看到,一级缓存也可以解决简单场景的循环依赖问题。
  • 其实 getBean,是整个解决循环依赖的核心内容,A 创建后填充属性时依赖 B,那么就去创建 B,在创建 B 开始填充时发现依赖于 A,但此时 A 这个半成品对象已经存放在缓存到singletonObjects 中了,所以 B 可以正常创建,在通过递归把 A 也创建完整了。

c.使用三级缓存

要理解 DefaultSingletonBeanRegistry 中的三级缓存,可以通过以下几个关键点和注解来帮助你理清思路。

1. 一级缓存(singletonObjects)

  • 定义:用于存放完全初始化的单例对象。
  • 作用:当你请求一个 bean 时,首先会在这里查找。如果存在,直接返回。
// 一级缓存,普通对象
private Map<String, Object> singletonObjects = new ConcurrentHashMap<>();

2. 二级缓存(earlySingletonObjects)

  • 定义:存放那些已经被实例化但尚未完全初始化的对象,通常是因为它们被依赖的其他对象还未完成初始化
  • 作用:当一级缓存中找不到时,会检查这个缓存。此时,只有在代理对象的情况下,才会出现在这个缓存中。
javaCopy Code// 二级缓存,提前暴漏对象,没有完全实例化的对象
protected final Map<String, Object> earlySingletonObjects = new HashMap<>();

3. 三级缓存(singletonFactories)

  • 定义:存放能够创建对象的工厂(ObjectFactory),这些工厂在初始化的过程中提供 bean 的实例
  • 作用:当一级和二级缓存都没有找到相应的 bean 时,将会尝试从这个缓存中获取对应的工厂方法,并使用它来创建对象。
javaCopy Code// 三级缓存,存放代理对象
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>();

4. 工作流程

  • 当调用 getSingleton(String beanName) 方法时,首先在一级缓存中查找该 bean。

  • 如果找不到,则检查二级缓存是否有提前曝光的对象。

  • 如果仍然找不到,则查看三级缓存中是否存在相应的对象工厂(ObjectFactory)。

    • 如果存在,则调用工厂的 getObject() 方法创建该 bean,并将其放入二级缓存中,随后移除三级缓存中的工厂。

5. 示例代码解析

以下是 getSingleton 方法的简化逻辑分析:

/**
 * DefaultSingletonBeanRegistry 是一个实现 SingletonBeanRegistry 接口的类,
 * 用于管理 Spring 应用程序中的单例 bean。
 * 它支持三级缓存机制,以提高单例 bean 的获取性能。
 */
public class DefaultSingletonBeanRegistry implements SingletonBeanRegistry {
​
    // 一级缓存,存储完全实例化的单例对象
    private Map<String, Object> singletonObjects = new ConcurrentHashMap<>();
​
    // 二级缓存,存储提前暴露的对象,这些对象可能还未完全实例化
    protected final Map<String, Object> earlySingletonObjects = new HashMap<>();
​
    // 三级缓存,存放代理对象的工厂
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>();
​
    // 存放可被销毁的 bean 的映射
    private final Map<String, DisposableBean> disposableBeans = new LinkedHashMap<>();
​
    /**
     * 获取指定名称的单例对象。如果对象不存在,将尝试从早期缓存或工厂中创建。
     *
     * @param beanName 需要获取的单例对象的名称
     * @return 单例对象,如果不存在则返回 null
     */
    @Override
    public Object getSingleton(String beanName) {
        // 从一级缓存中获取对象
        Object singletonObject = singletonObjects.get(beanName);
        if (singletonObject == null) {
            // 从二级缓存中获取对象
            singletonObject = earlySingletonObjects.get(beanName);
            // 判断二级缓存中是否有对象
            if (singletonObject == null) {
                // 从三级缓存中获取工厂并尝试创建对象
                ObjectFactory<?> singletonFactory = singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    singletonObject = singletonFactory.getObject();
                    // 将创建的对象放入二级缓存
                    earlySingletonObjects.put(beanName, singletonObject);
                    // 移除三级缓存中的工厂
                    singletonFactories.remove(beanName);
                }
            }
        }
        return singletonObject;
    }
​
    /**
     * 注册一个单例对象,将其放入一级缓存。
     *
     * @param beanName      要注册的单例对象的名称
     * @param singletonObject 注册的单例对象
     */
    public void registerSingleton(String beanName, Object singletonObject) {
        singletonObjects.put(beanName, singletonObject);
        // 清理早期缓存和三级缓存
        earlySingletonObjects.remove(beanName);
        singletonFactories.remove(beanName);
    }
​
    /**
     * 添加一个单例工厂,如果该 bean 尚未在一级缓存中存在。
     *
     * @param beanName       要注册的 bean 名称
     * @param singletonFactory 生成单例对象的工厂
     */
    protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
        if (!this.singletonObjects.containsKey(beanName)) {
            this.singletonFactories.put(beanName, singletonFactory);
            // 清理早期缓存
            earlySingletonObjects.remove(beanName);
        }
    }
​
    /**
     * 注册一个可被销毁的 bean,当容器关闭时自动调用其销毁方法。
     *
     * @param beanName bean 名称
     * @param bean 可被销毁的 bean 实例
     */
    public void registerDisposableBean(String beanName, DisposableBean bean) {
        disposableBeans.put(beanName, bean);
    }
​
}
​

6. 总结

  • 一级缓存:完全初始化的对象,用于直接返回。
  • 二级缓存:提前曝光的对象,主要用于解决循环依赖的问题。
  • 三级缓存:对象工厂,提供创建对象的能力。

这种设计模式使得 Spring 能够高效地管理 bean 的生命周期,同时解决了循环依赖的问题。希望这些注解能帮助你更好地理解三级缓存的运作!

d.业务的循环依赖问题

1.示例代码

假设我们有两个类 UserServiceOrderService,它们相互依赖。

jimport org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
​
@Component
class UserService {
    private OrderService orderService;
​
    @Autowired
    public void setOrderService(OrderService orderService) {
        this.orderService = orderService;
    }
​
    public void print() {
        System.out.println("UserService is using OrderService");
    }
}
​
@Component
class OrderService {
    private UserService userService;
​
    @Autowired
    public void setUserService(UserService userService) {
        this.userService = userService;
    }
​
    public void print() {
        System.out.println("OrderService is using UserService");
    }
}

2.循环依赖分析

在这个例子中:

  • UserService 依赖于 OrderService
  • OrderService 又依赖于 UserService

这种情况就形成了一个循环依赖。

3.Spring 处理流程

  1. 实例化 UserService

    • Spring 首先创建 UserService 的实例,并发现它需要 OrderService
  2. 存储到三级缓存

    • 在创建 UserService 时,Spring 会将其的 ObjectFactory 存放到三级缓存中。
  3. 实例化 OrderService

    • 然后,Spring 会创建 OrderService 的实例,并发现它需要 UserService(此时 UserService 还没有完全初始化)。
  4. 从二级缓存获取

    • 因为 UserService 在三级缓存中,Spring 会通过 ObjectFactory 获取到 UserService 的引用,而不是完全初始化的 Bean。这使得 OrderService 能够完成依赖注入。
  5. 完成初始化

    • OrderService 完成初始化后,Spring 会将其放入一级缓存中。
    • 接着,Spring 会返回到 UserService,并将 OrderService 注入到 UserService 中,完成其初始化并放入一级缓存。

4.结果

通过三级缓存,Spring 能够成功解决这个循环依赖问题,确保 UserServiceOrderService 都能够被正确地实例化并注入。

5.使用建议

  • 在实际应用中,应尽量避免循环依赖,因为它可能导致设计不清晰。可以考虑重构代码,将依赖关系解耦,或者使用设计模式(如观察者模式、策略模式等)来减少直接依赖。