最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Spring?循環(huán)依賴之AOP實(shí)現(xiàn)詳情

 更新時(shí)間:2022年07月07日 10:49:44   作者:??碼農(nóng)參上??  
這篇文章主要介紹了Spring?循環(huán)依賴之AOP實(shí)現(xiàn)詳情,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的盆友可以參考一下

前言:

我們接著上一篇文章繼續(xù)往下看,首先看一下下面的例子,前面的兩個(gè)serviceA和serviceB不變,我們添加一個(gè)BeanPostProcessor

@Component
public class MyPostProcessor implements BeanPostProcessor {
   @Override
   public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
       if (beanName.equals("serviceA")){
           System.out.println("create new ServiceA");
           return new ServiceA();
       }
       return bean;
   }
}

運(yùn)行一下,結(jié)果報(bào)錯(cuò)了:

Exception in thread "main" org.springframework.beans.factory.BeanCurrentlyInCreationException: 
Error creating bean with name 'serviceA': Bean with name 'serviceA' 
has been injected into other beans [serviceB] in its raw version as 
part of a circular reference, but has eventually been wrapped. This 
means that said other beans do not use the final version of the bean.
This is often the result of over-eager type matching - consider 
using 'getBeanNamesOfType' with the 'allowEagerInit' flag turned 
off, for example.

在分析錯(cuò)誤之前,我們?cè)偈崂硪幌抡Qh(huán)依賴的過(guò)程:

  • 1、初始化原生對(duì)象serviceA,放入三級(jí)緩存
  • 2、serviceA填充屬性,發(fā)現(xiàn)依賴serviceB,創(chuàng)建依賴對(duì)象
  • 3、創(chuàng)建serviceB,填充屬性發(fā)現(xiàn)依賴serviceA,從三級(jí)緩存中找到填充
  • 4、執(zhí)行serviceB的后置處理器和回調(diào)方法,放入單例池
  • 5、執(zhí)行serviceA的后置處理器和回調(diào)方法,放入單例池

再回頭看上面的錯(cuò)誤,大意為在循環(huán)依賴中我們給serviceB注入了serviceA,但是注入之后我們又在后置處理器中對(duì)serviceA進(jìn)行了包裝,因此導(dǎo)致了serviceB中注入的和最后生成的serviceA不一致。

但是熟悉aop的同學(xué)應(yīng)該知道,aop的底層也是利用后置處理器實(shí)現(xiàn)的啊,那么為什么aop就可以正常執(zhí)行呢?我們添加一個(gè)切面橫切serviceA的getServiceB方法:

@Component
@Aspect
public class MyAspect {
    @Around("execution(* com.hydra.service.ServiceA.getServiceB())")
    public void invoke(ProceedingJoinPoint pjp){
        try{
            System.out.println("execute aop around method");
            pjp.proceed();
        }catch (Throwable e){
            e.printStackTrace();
        }
    }
}

先不看運(yùn)行結(jié)果,代碼可以正常執(zhí)行不出現(xiàn)異常,那么aop是怎么實(shí)現(xiàn)的呢?

前面的流程和不使用aop相同,我們運(yùn)行到serviceB需要注入serviceA的地方,調(diào)用getSingleton方法從三級(jí)緩存中獲取serviceA存儲(chǔ)的singletonFactory,調(diào)用getEarlyBeanReference方法。在該方法中遍歷執(zhí)行SmartInstantiationAwareBeanPostProcessor后置處理器的getEarlyBeanReference方法:

看一下都有哪些類實(shí)現(xiàn)了這個(gè)方法:

在spring中,就是這個(gè)AbstractAutoProxyCreator負(fù)責(zé)實(shí)現(xiàn)了aop,進(jìn)入getEarlyBeanReference方法:

public Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
    //beanName
    Object cacheKey = getCacheKey(bean.getClass(), beanName);
    this.earlyProxyReferences.put(cacheKey, bean); 
    //產(chǎn)生代理對(duì)象
    return wrapIfNecessary(bean, beanName, cacheKey); 
}

earlyProxyReferences 是一個(gè)Map,用于緩存bean的原始對(duì)象,也就是執(zhí)行aop之前的bean,非常重要,在后面還會(huì)用到這個(gè)Map:

Map<Object, Object> earlyProxyReferences = new ConcurrentHashMap<>(16);

記住下面這個(gè)wrapIfNecessary方法,它才是真正負(fù)責(zé)生成代理對(duì)象的方法:

上面首先解析并拿到所有的切面,調(diào)用createProxy方法創(chuàng)建代理對(duì)象并返回。然后回到getSingleton方法中,將serviceA加入二級(jí)緩存,并從三級(jí)緩存中移除掉。

可以看到,二級(jí)緩存中的serviceA已經(jīng)是被cglib代理過(guò)的代理對(duì)象了,當(dāng)然這時(shí)的serviceA還是沒有屬性值填充的。

那么這里又會(huì)有一個(gè)問(wèn)題,我們之前講過(guò),在填充完屬性后,會(huì)調(diào)用后置處理器中的方法,而這些方法都是基于原始對(duì)象的,而不是代理對(duì)象。

在前一篇文章中我們也講過(guò),在initializeBean方法中會(huì)執(zhí)行后置處理器,并且正常情況下aop也是在這里完成的。那么我們就要面臨一個(gè)問(wèn)題,如果避免重復(fù)執(zhí)行aop的過(guò)程。在initializeBean方法中:

if (mbd == null || !mbd.isSynthetic()) {
  wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}

調(diào)用applyBeanPostProcessorsAfterInitialization,執(zhí)行所有后置處理器的after方法:

執(zhí)行AbstractAutoProxyCreatorpostProcessAfterInitialization方法:

public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) throws BeansException {
  if (bean != null) {
    Object cacheKey = getCacheKey(bean.getClass(), beanName);
    if (this.earlyProxyReferences.remove(cacheKey) != bean) {
      return wrapIfNecessary(bean, beanName, cacheKey);
    }
  }
  return bean;
}

earlyProxyReferences 我們之前說(shuō)過(guò)非常重要,它緩存了進(jìn)行aop之前的原始對(duì)象,并且這里參數(shù)傳入的Object也是原始對(duì)象。因此在這里執(zhí)行remove操作的判斷語(yǔ)句返回false,不會(huì)執(zhí)行if中的語(yǔ)句,不會(huì)再執(zhí)行一遍aop的過(guò)程。

回過(guò)頭來(lái)再梳理一下,因?yàn)橹斑M(jìn)行過(guò)循環(huán)依賴,所以提前執(zhí)行了AbstractAutoProxyCreatorgetEarlyBeanReference方法,執(zhí)行了aop的過(guò)程,在earlyProxyReferences中緩存了原生對(duì)象。因此在循環(huán)依賴的情況下,等式成立,直接返回。而在沒有循環(huán)依賴的普通情況下,earlyProxyReferences執(zhí)行remove返回為null,等式不成立,正常執(zhí)行aop流程。

需要注意的是,這個(gè)方法中最終返回的還是原始對(duì)象,而不是aop后的代理對(duì)象。執(zhí)行到這一步,我們先看一下嵌套的狀態(tài):

對(duì)外暴露的serviceA是原始對(duì)象,依賴的serviceB已經(jīng)被注入了。而serviceB中依賴的serviceA是代理對(duì)象,并且這個(gè)代理對(duì)象依賴的serviceB還沒有被注入。

向下執(zhí)行:

再次通過(guò)getSingleton獲取serviceA:

這次我們通過(guò)二級(jí)緩存就可以拿到之前經(jīng)過(guò)aop的代理對(duì)象,因此不用找三級(jí)緩存直接返回這個(gè)代理對(duì)象,并最終把這個(gè)代理對(duì)象添加到一級(jí)緩存單例池中。

到這,我們對(duì)三級(jí)緩存的作用做一個(gè)總結(jié):

  • 1、singletonObjects:?jiǎn)卫?,緩存了?jīng)過(guò)完整生命周期的bean
  • 2、earlySingletonObjects:緩存了提前曝光的原始對(duì)象,注意這里存的還不是bean,這里存的對(duì)象經(jīng)過(guò)了aop的代理,但是沒有執(zhí)行屬性的填充以及后置處理器方法的執(zhí)行
  • 3、singletonFactories:緩存的是ObjectFactory,主要用來(lái)去生成原始對(duì)象進(jìn)行了aop之后得到的代理對(duì)象。在每個(gè)bean的生成過(guò)程中,都會(huì)提前在這里緩存一個(gè)工廠。如果沒有出現(xiàn)循環(huán)依賴依賴這個(gè)bean,那么這個(gè)工廠不會(huì)起到作用,按照正常生命周期執(zhí)行,執(zhí)行完后直接把本bean放入一級(jí)緩存中。如果出現(xiàn)了循環(huán)依賴依賴了這個(gè)bean,沒有aop的情況下直接返回原始對(duì)象,有aop的情況下返回代理對(duì)象。

全部創(chuàng)建流程結(jié)束,看一下結(jié)果:

我們發(fā)現(xiàn),在生成的serviceA的cglib代理對(duì)象中,serviceB屬性值并沒有被填充,只有serviceB中serviceA的屬性填充成功了。

可以看到如果使用cglib,在代理對(duì)象的target中會(huì)包裹一個(gè)原始對(duì)象,而原始對(duì)象的屬性是被填充過(guò)的。

那么,如果不使用cglib代理,而使用jdk動(dòng)態(tài)代理呢?我們對(duì)之前的代碼進(jìn)行一下改造,添加兩個(gè)接口:

public interface IServiceA {
    public IServiceB getServiceB();
}
public interface IServiceB {
    public IServiceA getServiceA();
}

改造兩個(gè)Service類:

@Component
public class ServiceA implements IServiceA{
    @Autowired
    private IServiceB serviceB;

    public IServiceB getServiceB() {
        System.out.println("get ServiceB");
        return this.serviceB;
    }
}
@Component
public class ServiceB implements IServiceB{
    @Autowired
    private IServiceA serviceA;

    public IServiceA getServiceA() {
        System.out.println("get ServiceA");
        return serviceA;
    }
}

執(zhí)行結(jié)果:

看一下serviceA的詳細(xì)信息:

同樣也是在target中包裹了原生對(duì)象,并在原生對(duì)象中注入了serviceB的實(shí)例。

綜上兩種方法,可以看出在我們執(zhí)行serviceA的getServiceB方法時(shí),都無(wú)法正常獲取到其bean對(duì)象,都會(huì)返回一個(gè)null值。那么如果非要直接獲得這個(gè)serviceB應(yīng)該怎么辦呢?

我們可以通過(guò)反射的方式,先看cglib代理情況下:

ServiceA serviceA= (ServiceA) context.getBean("serviceA");
Field h = serviceA.getClass().getDeclaredField("CGLIB$CALLBACK_0");
h.setAccessible(true);
Object dynamicAdvisedInterceptor = h.get(serviceA);
Field advised = dynamicAdvisedInterceptor.getClass().getDeclaredField("advised");
advised.setAccessible(true);
Object target = ((AdvisedSupport)advised.get(dynamicAdvisedInterceptor)).getTargetSource().getTarget();
ServiceA serviceA1= (ServiceA) target;
System.out.println(serviceA1.getServiceB());

再看看jdk動(dòng)態(tài)代理情況下:

IServiceA serviceA = (IServiceA) context.getBean("serviceA");
Field h=serviceA.getClass().getSuperclass().getDeclaredField("h");
h.setAccessible(true);
AopProxy aopProxy = (AopProxy) h.get(serviceA);
Field advised = aopProxy.getClass().getDeclaredField("advised");
advised.setAccessible(true);
Object target = ((AdvisedSupport)advised.get(aopProxy)).getTargetSource().getTarget();
ServiceA serviceA1= (ServiceA) target;
System.out.println(serviceA1.getServiceB());

執(zhí)行結(jié)果都能獲取到serviceB的實(shí)例:

對(duì)aop情況下的循環(huán)依賴進(jìn)行一下總結(jié):spring專門為了處理aop情況下的循環(huán)依賴提供了特殊的解決方案,但是不論是使用jdk動(dòng)態(tài)代理還是cglib代理,都在代理對(duì)象的內(nèi)部包裹了原始對(duì)象,在原始對(duì)象中才有依賴的屬性。此外,如果我們使用了后置處理器對(duì)bean進(jìn)行包裝,循環(huán)依賴的問(wèn)題還是不能解決的。

總結(jié):

最后對(duì)本文的重點(diǎn)進(jìn)行一下總結(jié):

  • 1、spring通過(guò)借助三級(jí)緩存完成了循環(huán)依賴的實(shí)現(xiàn),這個(gè)過(guò)程中要清楚三級(jí)緩存分別在什么場(chǎng)景下發(fā)揮了什么具體作用
  • 2、產(chǎn)生aop情況下,調(diào)用后置處理器并將生成的代理對(duì)象提前曝光,并通過(guò)額外的一個(gè)緩存避免重復(fù)執(zhí)行aop
  • 3、二級(jí)緩存和三級(jí)緩存只有在產(chǎn)生循環(huán)依賴的情況下,才會(huì)真正起到作用
  • 4、此外,除去本文中提到的通過(guò)屬性的方式注入依賴的情況外,大家可能會(huì)好奇如果使用構(gòu)造函數(shù)能否實(shí)現(xiàn)循環(huán)依賴,結(jié)果是不可以的。具體的調(diào)用過(guò)程這里不再多說(shuō),有興趣的同學(xué)可以自己再對(duì)照源碼進(jìn)行一下梳理。

到此這篇關(guān)于Spring 循環(huán)依賴之AOP實(shí)現(xiàn)詳情的文章就介紹到這了,更多相關(guān)Spring 循環(huán)依賴 AOP 實(shí)現(xiàn)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java虛擬機(jī)JVM優(yōu)化實(shí)戰(zhàn)的過(guò)程全記錄

    Java虛擬機(jī)JVM優(yōu)化實(shí)戰(zhàn)的過(guò)程全記錄

    有人說(shuō)Java之所以能夠崛起,JVM功不可沒。Java虛擬機(jī)最初服務(wù)于讓Java語(yǔ)言凌駕于平臺(tái)之上,實(shí)現(xiàn)“編寫一次,到處運(yùn)行”,那么下面這篇文章主要給大家分享了個(gè)關(guān)于Java虛擬機(jī)JVM優(yōu)化實(shí)戰(zhàn)的過(guò)程全記錄,需要的朋友可以參考借鑒,下面來(lái)一起看看吧。
    2017-08-08
  • 淺談java中文本框和文本區(qū)

    淺談java中文本框和文本區(qū)

    本文給大家介紹的是java中的文本框和文本區(qū)的概念和使用方法,以及簡(jiǎn)單的示例,十分實(shí)用,有需要的小伙伴可以參考下。
    2015-06-06
  • java中switch條件語(yǔ)句的三種語(yǔ)法、用法及支持的參數(shù)類型

    java中switch條件語(yǔ)句的三種語(yǔ)法、用法及支持的參數(shù)類型

    Java中的switch語(yǔ)句是一種多分支選擇結(jié)構(gòu),可以一個(gè)變量的值改變程序的控制流,這篇文章主要給大家介紹了關(guān)于java中switch條件語(yǔ)句的三種語(yǔ)法、用法及支持的參數(shù)類型的相關(guān)資料,需要的朋友可以參考下
    2024-06-06
  • Java核心技術(shù)之反射

    Java核心技術(shù)之反射

    本文非常詳細(xì)的講解了java反射的相關(guān)資料,java反射在現(xiàn)今的使用中很頻繁,希望此文可以幫大家解答疑惑,可以幫助大家理解
    2021-11-11
  • Java實(shí)現(xiàn)Excel與HTML互轉(zhuǎn)

    Java實(shí)現(xiàn)Excel與HTML互轉(zhuǎn)

    Excel是一種電子表格格式,而HTM則是一種用于創(chuàng)建網(wǎng)頁(yè)的標(biāo)記語(yǔ)言,雖然兩者在用途上存在差異,但有時(shí)我們需要將數(shù)據(jù)從一種格式轉(zhuǎn)換為另一種格式,下面我們就來(lái)看看具體實(shí)現(xiàn)方法吧
    2025-01-01
  • SpringBoot集成JWT實(shí)現(xiàn)登陸驗(yàn)證的方法詳解

    SpringBoot集成JWT實(shí)現(xiàn)登陸驗(yàn)證的方法詳解

    JSON?Web?Token(JWT)是一個(gè)開放的標(biāo)準(zhǔn)(RFC?7519),它定義了一個(gè)緊湊且自包含的方式,用于在各方之間以JSON對(duì)象安全地傳輸信息。本文將利用SpringBoot集成JWT實(shí)現(xiàn)登陸驗(yàn)證,感興趣的可以了解一下
    2022-05-05
  • Spring Boot+maven打war包的方法

    Spring Boot+maven打war包的方法

    這篇文章主要介紹了Spring Boot+maven打war包的方法,本文通過(guò)實(shí)例代碼相結(jié)合的形式給大家介紹的非常詳細(xì),需要的朋友參考下吧
    2018-05-05
  • Java字符判斷的小例子

    Java字符判斷的小例子

    從鍵盤上輸入一個(gè)字符串,遍歷該字符串中的每個(gè)字符,若該字符為小寫字母,則輸出“此字符是小寫字母”;若為大寫字母,則輸出“此字符為大寫字母”;否則輸出“此字符不是字母”
    2013-09-09
  • 深入探討Java內(nèi)存區(qū)域

    深入探討Java內(nèi)存區(qū)域

    本篇文章對(duì)Java內(nèi)存區(qū)域的使用進(jìn)行了詳細(xì)的介紹,內(nèi)容很全面,需要的朋友可以參考下
    2015-07-07
  • LibrarySystem圖書管理系統(tǒng)(二)

    LibrarySystem圖書管理系統(tǒng)(二)

    這篇文章主要為大家詳細(xì)介紹了LibrarySystem圖書管理系統(tǒng)開發(fā)第二篇,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-05-05

最新評(píng)論

抚松县| 双鸭山市| 石阡县| 怀宁县| 邻水| 武隆县| 富源县| 林西县| 精河县| 肃南| 青州市| 会昌县| 罗甸县| 陇川县| 察雅县| 独山县| 鹰潭市| 林周县| 天全县| 苏州市| 安徽省| 原平市| 彭水| 增城市| 永吉县| 广宁县| 长宁区| 泸定县| 游戏| 嘉峪关市| 崇信县| 临西县| 延川县| 九江县| 华阴市| 思茅市| 密山市| 平顶山市| 高安市| 禹城市| 永兴县|