关于java:spring循环依赖与三级缓存

42次阅读

共计 7037 个字符,预计需要花费 18 分钟才能阅读完成。

什么是循环依赖?
循环依赖其实就是循环援用,也就是两个或则两个以上的 bean 相互持有对方,最终造成闭环。比方 A 依赖于 B,B 依赖于 C,C 又依赖于 A。

image.png

能够构想一下这个场景:如果在日常开发中咱们用 new 对象的形式,若构造函数之间产生这种循环依赖的话,程序会在运行时始终循环调用最终导致内存溢出,示例代码如下:

public class Main {public static void main(String[] args) throws Exception {System.out.println(new A());
    }
}

class A {public A() {new B();
    }
}

class B {public B() {new A();
    }
} 

运行后果会抛出 Exception in thread “main” java.lang.StackOverflowError 异样
这是一个典型的循环依赖问题。本文说一下 Spring 是如果奇妙的解决平时咱们会遇到的 三大循环依赖问题 的~

Spring Bean 的循环依赖

谈到 Spring Bean 的循环依赖,有的小伙伴可能比拟生疏,毕竟开发过程中如同对 循环依赖 这个概念 无感知 。其实不然,你有这种错觉,权是因为你工作在 Spring 的 襁褓 中,从而让你“居安思危”~
我非常深信,小伙伴们在平时业务开发中肯定肯定写过如下构造的代码:
field 属性注入(setter 办法注入)循环依赖
这种形式是咱们最为罕用的依赖注入形式

@Service
class A {
    @Autowired
    private B b;
}

@Service
class B {
    @Autowired
    private A a;
} 

这其实就是 Spring 环境下典型的循环依赖场景。然而很显然,这种循环依赖场景,Spring 曾经完满的帮咱们解决和躲避了问题。所以即便平时咱们这样循环援用,也可能整成进行咱们的 coding 之旅~

Spring 中结构器依赖场演示

在 Spring 环境中,因为咱们的 Bean 的实例化、初始化都是交给了容器,因而它的循环依赖次要体现为上面三种场景。为了不便演示,我筹备了如下两个类:

@Service
public class A {public A(B b) {}}
@Service
public class B {public B(A a) {}} 

后果:我的项目启动失败抛出异样 BeanCurrentlyInCreationException

结构器注入形成的循环依赖,此种循环依赖形式是无奈解决的,只能抛出 BeanCurrentlyInCreationException 异样示意循环依赖。这也是结构器注入的最大劣势。

根本原因:Spring 解决循环依赖依附的是 Bean 的“两头态”这个概念,而这个两头态指的是曾经实例化,但还没初始化的状态。而结构器是实现实例化的,所以结构器的循环依赖无奈解决

对 Bean 的创立最为外围三个办法解释如下:

  • createBeanInstance:例化,其实也就是调用对象的 构造方法 实例化对象
  • populateBean:填充属性,这一步次要是对 bean 的依赖属性进行注入(@Autowired)
  • initializeBean:回到一些形如 initMethodInitializingBean 等办法

从对单例 Bean 的初始化能够看出,循环依赖次要产生在 第二步(populateBean),也就是 field 属性注入的解决。

Spring 容器的三级缓存

在 Spring 容器的整个申明周期中,单例 Bean 有且仅有一个对象。这很容易让人想到能够用缓存来减速拜访。
从源码中也能够看出 Spring 大量使用了 Cache 的伎俩,在循环依赖问题的解决过程中甚至不惜应用了“三级缓存”,这也便是它设计的精妙之处~

三级缓存 其实它更像是 Spring 容器工厂的内的 术语,采纳三级缓存模式来解决循环依赖问题,这三级缓存别离指:

public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
    ...
    // 从上至下 分表代表这“三级缓存”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); // 三级缓存
    ...
    
    /** Names of beans that are currently in creation. */
    // 这个缓存也非常重要:它示意 bean 创立过程中都会在外面呆着~
    // 它在 Bean 开始创立时放值,创立实现时会将其移出~
    private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>(16));

    /** Names of beans that have already been created at least once. */
    // 当这个 Bean 被创立实现后,会标记为这个 留神:这里是 set 汇合 不会反复
    // 至多被创立了一次的  都会放进这里~~~~
    private final Set<String> alreadyCreated = Collections.newSetFromMap(new ConcurrentHashMap<>(256)); 

注:AbstractBeanFactory 继承自 DefaultSingletonBeanRegistry~

singletonObjects:用于寄存齐全初始化好的 bean,从该缓存中取出的 bean 能够间接应用
earlySingletonObjects:提前曝光的单例对象的 cache,寄存原始的 bean 对象(尚未填充属性),用于解决循环依赖
singletonFactories:单例对象工厂的 cache,寄存 bean 工厂对象,用于解决循环依赖

public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
    ...
    @Override
    @Nullable
    public Object getSingleton(String beanName) {return getSingleton(beanName, true);
    }
    @Nullable
    protected Object getSingleton(String beanName, boolean allowEarlyReference) {Object singletonObject = this.singletonObjects.get(beanName);
        if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {synchronized (this.singletonObjects) {singletonObject = this.earlySingletonObjects.get(beanName);
                if (singletonObject == null && allowEarlyReference) {ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                    if (singletonFactory != null) {singletonObject = singletonFactory.getObject();
                        this.earlySingletonObjects.put(beanName, singletonObject);
                        this.singletonFactories.remove(beanName);
                    }
                }
            }
        }
        return singletonObject;
    }
    ...
    public boolean isSingletonCurrentlyInCreation(String beanName) {return this.singletonsCurrentlyInCreation.contains(beanName);
    }
    protected boolean isActuallyInCreation(String beanName) {return isSingletonCurrentlyInCreation(beanName);
    }
    ...
} 

先从一级缓存 singletonObjects 中去获取。(如果获取到就间接 return)
如果获取不到或者对象正在创立中(isSingletonCurrentlyInCreation()),那就再从二级缓存 earlySingletonObjects 中获取。(如果获取到就间接 return)
如果还是获取不到,且容许 singletonFactories(allowEarlyReference=true)通过 getObject()获取。就从三级缓存 singletonFactory.getObject()获取。(如果获取到了就从 singletonFactories 中移除,并且放进 earlySingletonObjects。其实也就是从三级缓存挪动(是剪切、不是复制哦~)到了二级缓存)
退出 singletonFactories 三级缓存的前提是执行了结构器,所以结构器的循环依赖没法解决

getSingleton()从缓存里获取单例对象步骤剖析可知,Spring 解决循环依赖的窍门:就在于 singletonFactories 这个三级缓存。这个 Cache 外面都是 ObjectFactory,它是解决问题的要害。

为什么要用三级缓存而不是二级缓存

image.png

能够看到三级缓存各自保留的对象,这里重点关注二级缓存 earlySingletonObjects 和三级缓存 singletonFactory,一级缓存能够进行疏忽。后面咱们讲过先实例化的 bean 会通过 ObjectFactory 半成品提前裸露在三级缓存中
所以如果没有 AOP 的话的确能够两级缓存就能够解决循环依赖的问题,如果加上 AOP,两级缓存是无奈解决的,不可能每次执行 singleFactory.getObject()办法都给我产生一个新的代理对象,所以还要借助另外一个缓存来保留产生的代理对象

动态代理

动态代理的特点是, 为每一个业务加强都提供一个代理类, 由代理类来创立代理对象. 上面咱们通过动态代理来实现对转账业务进行身份验证.

(1) 转账业务

public interface IAccountService {
    // 主业务逻辑: 转账
    void transfer();}
public class AccountServiceImpl implements IAccountService {
    @Override
    public void transfer() {System.out.println("调用 dao 层, 实现转账主业务.");
    }
} 

(2) 代理类

public class AccountProxy implements IAccountService {
    // 指标对象
    private IAccountService target;

    public AccountProxy(IAccountService target) {this.target = target;}

    /**
     * 代理办法, 实现对指标办法的性能加强
     */
    @Override
    public void transfer() {before();
        target.transfer();}

    /**
     * 前置加强
     */
    private void before() {System.out.println("对转账人身份进行验证.");
    }
} 

(3) 测试

public class Client {public static void main(String[] args) {
        // 创立指标对象
        IAccountService target = new AccountServiceImpl();
        // 创立代理对象
        AccountProxy proxy = new AccountProxy(target);
        proxy.transfer();}
}

后果: 
对转账人身份进行验证.
调用 dao 层, 实现转账主业务. 

动静代理

动态代理会为每一个业务加强都提供一个代理类, 由代理类来创立代理对象, 而动静代理并不存在代理类, 代理对象间接由代理生成工具动静生成.

JDK 动静代理

JDK 动静代理是应用 java.lang.reflect 包下的代理类来实现. JDK 动静代理动静代理必须要有接口.

(1) 转账业务

public interface IAccountService {
    // 主业务逻辑: 转账
    void transfer();}
public class AccountServiceImpl implements IAccountService {
    @Override
    public void transfer() {System.out.println("调用 dao 层, 实现转账主业务.");
    }
} 

(2) 加强

因为这里没有配置切入点, 称为切面会有点奇怪, 所以称为加强.

public class AccountAdvice implements InvocationHandler {
    // 指标对象
    private IAccountService target;

    public AccountAdvice(IAccountService target) {this.target = target;}

    /**
     * 代理办法, 每次调用指标办法时都会进到这里
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {before();
        return method.invoke(target, args);
    }

    /**
     * 前置加强
     */
    private void before() {System.out.println("对转账人身份进行验证.");
    }
} 

(3) 测试

public class Client {public static void main(String[] args) {
        // 创立指标对象
        IAccountService target = new AccountServiceImpl();
        // 创立代理对象
        IAccountService proxy = (IAccountService) Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new AccountAdvice(target)
        );
        proxy.transfer();}
}
后果: 
对转账人身份进行验证.
调用 dao 层, 实现转账主业务. 

CGLIB 动静代理

JDK 动静代理必须要有接口, 但如果要代理一个没有接口的类该怎么办呢? 这时咱们能够应用 CGLIB 动静代理. CGLIB 动静代理的原理是生成指标类的子类, 这个子类对象就是代理对象, 代理对象是被加强过的.

留神: 不论有没有接口都能够应用 CGLIB 动静代理, 而不是只有在无接口的状况下能力应用.

(1) 转账业务

public class AccountService {public void transfer() {System.out.println("调用 dao 层, 实现转账主业务.");
    }
} 

(2) 加强

因为这里没有配置切入点, 称为切面会有点奇怪, 所以称为加强.

public class AccountAdvice implements MethodInterceptor {
    /**
     * 代理办法, 每次调用指标办法时都会进到这里
     */
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {before();
        return methodProxy.invokeSuper(obj, args);
        //        return method.invoke(obj, args);  这种也行
    }

    /**
     * 前置加强
     */
    private void before() {System.out.println("对转账人身份进行验证.");
    }
} 

(3) 测试

public class Client {public static void main(String[] args) {
        // 创立指标对象
        AccountService target = new AccountService();
        //
        // 创立代理对象
        AccountService proxy = (AccountService) Enhancer.create(target.getClass(),
                new AccountAdvice());
        proxy.transfer();}
}
后果: 
对转账人身份进行验证.
调用 dao 层, 实现转账主业务.

正文完
 0