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層,完成轉帳主業務.
相關文章
相關標籤/搜索