什麼是循環依賴?
循環依賴其實就是循環引用,也就是兩個或則兩個以上的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的襁褓
中,從而讓你「高枕無憂」~
我十分堅信,小夥伴們在平時業務開發中必定必定寫過以下結構的代碼:
field屬性注入(setter方法注入)循環依賴
這種方式是咱們最爲經常使用的依賴注入方式工具
@Service class A { @Autowired private B b; } @Service class B { @Autowired private A a; }
這其實就是Spring環境下典型的循環依賴場景。可是很顯然,這種循環依賴場景,Spring已經完美的幫咱們解決和規避了問題。因此即便平時咱們這樣循環引用,也可以整成進行咱們的coding之旅~測試
在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
:回到一些形如initMethod
、InitializingBean
等方法從對單例Bean的初始化能夠看出,循環依賴主要發生在第二步(populateBean),也就是field屬性注入的處理。
在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層,完成轉帳主業務.