spring循环依赖与三级缓存

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

image.png缓存

能够设想一下这个场景:若是在平常开发中咱们用new对象的方式,若构造函数之间发生这种循环依赖的话,程序会在运行时一直循环调用最终致使内存溢出,示例代码以下:ide

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的实例化、初始化都是交给了容器,所以它的循环依赖主要表现为下面三种场景。为了方便演示,我准备了以下两个类:this

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

结果:项目启动失败抛出异常BeanCurrentlyInCreationExceptionspa

构造器注入构成的循环依赖,此种循环依赖方式是没法解决的,只能抛出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层,完成转帐主业务.
相关文章
相关标签/搜索