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

詳解Spring?@Lazy注解為什么能破解死循環(huán)

 更新時(shí)間:2023年07月20日 10:47:33   作者:江南一點(diǎn)雨  
這篇文章主要來和大家探討一下Spring中的@Lazy注解為什么能破解死循環(huán),文中的示例代碼講解詳細(xì),具有一定的參考價(jià)值,需要的可以了解一下

以下內(nèi)容基于 Spring6.0.4。

上篇文章松哥和大家聊了在 Spring 中并非所有的循環(huán)依賴都可以被解決,有一些循環(huán)依賴默認(rèn)情況下 Spring 也是完全無法解決的。不熟悉的小伙伴可以先看看上篇文章。

上篇文章第一小節(jié)的案例為例,在構(gòu)造方法中互相注入對方的 Bean,此時(shí)完全就是一個(gè)死循環(huán)呀,對于這種死循環(huán),難道真的有辦法解決?

Spring 里邊提供了辦法來解決,但是似乎又沒有解決,為什么這么說,看完本文你就明白了。

1. @Lazy

如本文題目所示,上篇文章涉及到的三種無法自動解決的循環(huán)依賴,都可以通過添加 @Lazy 注解來解決。

如果是構(gòu)造器注入,如下:

@Service
public?class?AService?{
????BService?bService;
????@Lazy
????public?AService(BService?bService)?{
????????this.bService?=?bService;
????}
????public?BService?getbService()?{
????????return?bService;
????}
}
@Service
public?class?BService?{
????AService?aService;
????@Lazy
????public?BService(AService?aService)?{
????????this.aService?=?aService;
????}
????public?AService?getaService()?{
????????return?aService;
????}
}

@Lazy 注解可以添加在 AService 或者 BService 的構(gòu)造方法上,也可以都添加上。

添加上之后,我們再去啟動項(xiàng)目,就不會報(bào)錯了。這樣看起來問題解決了,但是其實(shí)還是差點(diǎn)意思,小伙伴們看一下我的啟動代碼:

ClassPathXmlApplicationContext?ctx?=?new?ClassPathXmlApplicationContext("aop.xml");
AService?aService?=?ctx.getBean(AService.class);
BService?bService?=?ctx.getBean(BService.class);
System.out.println("aService.getClass()?=?"?+?aService.getClass());
System.out.println("bService.getClass()?=?"?+?bService.getClass());
System.out.println("aService.getbService().getClass()?=?"?+?aService.getbService().getClass());
System.out.println("bService.getaService().getClass()?=?"?+?bService.getaService().getClass());

最終打印結(jié)果如下:

小伙伴們看到,我們從 AService 和 BService 中獲取到的 Bean 都是正常的未被代理的對象,事實(shí)上我們的原始代碼確實(shí)也沒有需要代理的地方。但是,AService 中的 BService 以及 BService 中的 AService 卻都是代理對象,按理說 AService 中的 BService 應(yīng)該和我們從 Spring 容器中獲取到的 BService 一致,BService 中的 AService 也應(yīng)該和 Spring 容器中獲取到的 AService 一致,但實(shí)際上,兩者卻并不相同。

不過這樣也好懂了,為什么 Spring 能把一個(gè)死結(jié)給解開,就是因?yàn)?AService 和 BService 各自注入的 Bean 都不是原始的 Bean,都是一個(gè)代理的 Bean,AService 中注入的 BService 是一個(gè)代理對象,同理,BService 中注入的 AService 也是一個(gè)代理對象。

這也是為什么我一開始說這個(gè)問題 Spring 解決了又沒解決。

其實(shí),這就是 @Lazy 這個(gè)注解的工作原理,看名字,加了該注解的對象會被延遲加載,實(shí)際上被該注解標(biāo)記的對象,會自動生成一個(gè)代理對象。

上篇文章中提到的另外兩個(gè)問題,也可以通過 @Lazy 注解來解決,代碼如下:

@Service
@Scope("prototype")
public?class?AService?{
????@Lazy
????@Autowired
????BService?bService;
}
@Service
@Scope("prototype")
public?class?BService?{
????@Lazy
????@Autowired
????AService?aService;
}

這里 @Lazy 只要一個(gè)其實(shí)就能解決問題,也可以兩個(gè)都添加。

對于含有 @Async 注解的情況,也可以通過 @Lazy 注解來解決:

@Service
public?class?AService?{
????@Autowired
????@Lazy
????BService?bService;
????@Async
????public?void?hello()?{
????????bService.hello();
????}
????public?BService?getbService()?{
????????return?bService;
????}
}
@Service
public?class?BService?{
????@Autowired
????AService?aService;
????public?void?hello()?{
????????System.out.println("xxx");
????}
????public?AService?getaService()?{
????????return?aService;
????}
}

如此,循環(huán)依賴可破!

總而言之一句話,@Lazy 注解是通過建立一個(gè)中間代理層,來破解循環(huán)依賴的。

2. 原理分析

接下來我們再來分析一下 @Lazy 注解處理的源碼。

這塊的源碼分析我就不從頭開始分析了,因?yàn)檎麄€(gè)處理流程前面部分和之前文章 @Autowired 到底是怎么把變量注入進(jìn)來的?所介紹的內(nèi)容是一致的,不熟悉的小伙伴建議先閱讀 @Autowired 到底是怎么把變量注入進(jìn)來的?一文。我這里就借用該文的總結(jié),帶領(lǐng)小伙伴們稍微回顧一下屬性注入的過程:

1.在創(chuàng)建 Bean 的時(shí)候,原始 Bean 創(chuàng)建出來之后,會調(diào)用 populateBean 方法進(jìn)行 Bean 的屬性填充。

2.接下來調(diào)用 postProcessAfterInstantiation 方法去判斷是否需要執(zhí)行后置處理器,如果不需要,就直接返回了。

3.調(diào)用 postProcessProperties 方法,去觸發(fā)各種后置處理器的執(zhí)行。

4.在第 3 步的方法中,調(diào)用 findAutowiringMetadata,這個(gè)方法又會進(jìn)一步觸發(fā) buildAutorwiringMetadata 方法,去找到包含了 @Autowired、@Value 以及 @Inject 注解的屬性或者方法,并將之封裝為 InjectedElement 返回。

5.調(diào)用 InjectedElement#inject 方法進(jìn)行屬性注入。

6.接下來執(zhí)行 resolvedCachedArgument 方法嘗試從緩存中找到需要的 Bean 對象。

7.如果緩存中不存在,則調(diào)用 resolveFieldValue 方法去容器中找到 Bean。

8.最后調(diào)用 makeAccessible 和 set 方法完成屬性的賦值。

在第 7 步中,調(diào)用 resolveFieldValue 方法去解析 Bean,@Lazy 注解的相關(guān)邏輯就是在這個(gè)方法中進(jìn)行處理的(對應(yīng) @Autowired 到底是怎么把變量注入進(jìn)來的?一文的 3.2 小節(jié))。

resolveFieldValue 方法最終會執(zhí)行到 resolveDependency 方法:

@Nullable
public?Object?resolveDependency(DependencyDescriptor?descriptor,?@Nullable?String?requestingBeanName,
??@Nullable?Set<String>?autowiredBeanNames,?@Nullable?TypeConverter?typeConverter)?throws?BeansException?{
?descriptor.initParameterNameDiscovery(getParameterNameDiscoverer());
?if?(Optional.class?==?descriptor.getDependencyType())?{
??return?createOptionalDependency(descriptor,?requestingBeanName);
?}
?else?if?(ObjectFactory.class?==?descriptor.getDependencyType()?||
???ObjectProvider.class?==?descriptor.getDependencyType())?{
??return?new?DependencyObjectProvider(descriptor,?requestingBeanName);
?}
?else?if?(javaxInjectProviderClass?==?descriptor.getDependencyType())?{
??return?new?Jsr330Factory().createDependencyProvider(descriptor,?requestingBeanName);
?}
?else?{
??Object?result?=?getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(
????descriptor,?requestingBeanName);
??if?(result?==?null)?{
???result?=?doResolveDependency(descriptor,?requestingBeanName,?autowiredBeanNames,?typeConverter);
??}
??return?result;
?}
}

在這個(gè)方法中,首先會判斷注入的屬性類型是 Optional、ObjectFactory 還是 JSR-330 中的注解,我們這里都不是,所以走最后一個(gè)分支。

在最后一個(gè) else 中,首先調(diào)用 getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary 方法看一下是否需要延遲加載 Bean 對象,@Lazy 注解就是在這里進(jìn)行處理的。如果能夠延遲加載,那么該方法的返回值就不為 null,就可以直接返回了,就不需要執(zhí)行 doResolveDependency 方法了。

ContextAnnotationAutowireCandidateResolver#getLazyResolutionProxyIfNecessary:

@Override
@Nullable
public?Object?getLazyResolutionProxyIfNecessary(DependencyDescriptor?descriptor,?@Nullable?String?beanName)?{
?return?(isLazy(descriptor)???buildLazyResolutionProxy(descriptor,?beanName)?:?null);
}

大家看一下,這個(gè)方法首先會調(diào)用 isLazy 去判斷一下是否需要延遲加載,如果需要,則調(diào)用 buildLazyResolutionProxy 方法構(gòu)建一個(gè)延遲加載的對象;如果不需要,則直接返回一個(gè) null 即可。

protected?boolean?isLazy(DependencyDescriptor?descriptor)?{
?for?(Annotation?ann?:?descriptor.getAnnotations())?{
??Lazy?lazy?=?AnnotationUtils.getAnnotation(ann,?Lazy.class);
??if?(lazy?!=?null?&&?lazy.value())?{
???return?true;
??}
?}
?MethodParameter?methodParam?=?descriptor.getMethodParameter();
?if?(methodParam?!=?null)?{
??Method?method?=?methodParam.getMethod();
??if?(method?==?null?||?void.class?==?method.getReturnType())?{
???Lazy?lazy?=?AnnotationUtils.getAnnotation(methodParam.getAnnotatedElement(),?Lazy.class);
???if?(lazy?!=?null?&&?lazy.value())?{
????return?true;
???}
??}
?}
?return?false;
}

這個(gè)判斷方法主要是檢查當(dāng)前類中各種參數(shù)上是否含有 @Lazy 注解、方法、屬性以及類名上是否含有 @Lazy 注解,如果有,則返回 true,否則返回 false。

再來看 buildLazyResolutionProxy 方法:

private?Object?buildLazyResolutionProxy(
??final?DependencyDescriptor?descriptor,?final?@Nullable?String?beanName,?boolean?classOnly)?{
?BeanFactory?beanFactory?=?getBeanFactory();
?final?DefaultListableBeanFactory?dlbf?=?(DefaultListableBeanFactory)?beanFactory;
?TargetSource?ts?=?new?TargetSource()?{
??@Override
??public?Class<?>?getTargetClass()?{
???return?descriptor.getDependencyType();
??}
??@Override
??public?boolean?isStatic()?{
???return?false;
??}
??@Override
??public?Object?getTarget()?{
???Set<String>?autowiredBeanNames?=?(beanName?!=?null???new?LinkedHashSet<>(1)?:?null);
???Object?target?=?dlbf.doResolveDependency(descriptor,?beanName,?autowiredBeanNames,?null);
???if?(target?==?null)?{
????Class<?>?type?=?getTargetClass();
????if?(Map.class?==?type)?{
?????return?Collections.emptyMap();
????}
????else?if?(List.class?==?type)?{
?????return?Collections.emptyList();
????}
????else?if?(Set.class?==?type?||?Collection.class?==?type)?{
?????return?Collections.emptySet();
????}
????throw?new?NoSuchBeanDefinitionException(descriptor.getResolvableType(),
??????"Optional?dependency?not?present?for?lazy?injection?point");
???}
???if?(autowiredBeanNames?!=?null)?{
????for?(String?autowiredBeanName?:?autowiredBeanNames)?{
?????if?(dlbf.containsBean(autowiredBeanName))?{
??????dlbf.registerDependentBean(autowiredBeanName,?beanName);
?????}
????}
???}
???return?target;
??}
??@Override
??public?void?releaseTarget(Object?target)?{
??}
?};
?ProxyFactory?pf?=?new?ProxyFactory();
?pf.setTargetSource(ts);
?Class<?>?dependencyType?=?descriptor.getDependencyType();
?if?(dependencyType.isInterface())?{
??pf.addInterface(dependencyType);
?}
?ClassLoader?classLoader?=?dlbf.getBeanClassLoader();
?return?(classOnly???pf.getProxyClass(classLoader)?:?pf.getProxy(classLoader));
}

這個(gè)方法就是用來生成代理的對象的,這里構(gòu)建了代理對象 TargetSource,在其 getTarget 方法中,會去執(zhí)行 doResolveDependency 獲取到被代理的對象(doResolveDependency 的獲取邏輯可以參考@Autowired 到底是怎么把變量注入進(jìn)來的?一文),而 getTarget 方法只有在需要的時(shí)候才會被調(diào)用。所以,@Lazy 注解所做的事情,就是在給 Bean 中的各個(gè)屬性注入值的時(shí)候,原本需要去 Spring 容器中找注入的對象,現(xiàn)在不找了,先給一個(gè)代理對象頂著,需要的時(shí)候再去 Spring 容器中查找。

以上就是詳解Spring @Lazy注解為什么能破解死循環(huán)的詳細(xì)內(nèi)容,更多關(guān)于Spring @Lazy的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Java多線程案例之定時(shí)器詳解

    Java多線程案例之定時(shí)器詳解

    定時(shí)器也是軟件開發(fā)中的一個(gè)重要組件.?類似于一個(gè)?“鬧鐘”.?達(dá)到一個(gè)設(shè)定的時(shí)間之后,?就執(zhí)行某個(gè)指定好的代碼。本文就來和大家詳細(xì)聊聊定時(shí)器的原理與使用,感興趣的可以了解一下
    2022-10-10
  • SpringBoot定義過濾器、監(jiān)聽器、攔截器的方法

    SpringBoot定義過濾器、監(jiān)聽器、攔截器的方法

    本篇文章主要介紹了SpringBoot定義過濾器、監(jiān)聽器、攔截器的方法,具有一定的參考價(jià)值,有興趣的可以了解一下。
    2017-04-04
  • springboot整合ehcache 實(shí)現(xiàn)支付超時(shí)限制的方法

    springboot整合ehcache 實(shí)現(xiàn)支付超時(shí)限制的方法

    在線支付系統(tǒng)需要極高的穩(wěn)定性,在有限的系統(tǒng)資源下,穩(wěn)定性優(yōu)先級要高于系統(tǒng)并發(fā)以及用戶體驗(yàn),因此需要合理的控制用戶的支付請求。下面通過本文給大家介紹springboot整合ehcache 實(shí)現(xiàn)支付超時(shí)限制的方法,一起看看吧
    2018-01-01
  • IDEA中的Run/Debug Configurations各項(xiàng)解讀

    IDEA中的Run/Debug Configurations各項(xiàng)解讀

    這篇文章主要介紹了IDEA中的Run/Debug Configurations各項(xiàng)解讀,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-09-09
  • Springboot編寫CRUD時(shí)訪問對應(yīng)數(shù)據(jù)函數(shù)返回null的問題及解決方法

    Springboot編寫CRUD時(shí)訪問對應(yīng)數(shù)據(jù)函數(shù)返回null的問題及解決方法

    我在學(xué)習(xí)springboot,其中在編寫CRUD時(shí)發(fā)現(xiàn)訪問數(shù)據(jù)的函數(shù)執(zhí)行下去返回值是null但是其它部分正常,這篇文章主要介紹了Springboot在編寫CRUD時(shí),訪問對應(yīng)數(shù)據(jù)函數(shù)返回null,需要的朋友可以參考下
    2024-02-02
  • Java函數(shù)式編程(十):收集器

    Java函數(shù)式編程(十):收集器

    這篇文章主要介紹了Java函數(shù)式編程(十):收集器,本文是系列文章的第10篇,其它文章請參閱本文底部的相關(guān)文章,需要的朋友可以參考下
    2014-09-09
  • 基于Graphics2D drawImage圖片失真的解決方案

    基于Graphics2D drawImage圖片失真的解決方案

    這篇文章主要介紹了基于Graphics2D drawImage圖片失真的解決方案,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-11-11
  • idea在某天打開后一直在updating index的問題及解決

    idea在某天打開后一直在updating index的問題及解決

    idea長時(shí)間updatingindexs無法加載項(xiàng)目,原因是緩存文件異常或被安全軟件清理,解決方法是點(diǎn)擊File>InvalidateCaches...>InvalidateandRestart重新加載,或等待idea自動裝配完畢
    2026-05-05
  • Spring Boot 實(shí)現(xiàn)程序的優(yōu)雅退出(詳細(xì)步驟)

    Spring Boot 實(shí)現(xiàn)程序的優(yōu)雅退出(詳細(xì)步驟)

    Spring Boot 為我們提供了優(yōu)雅退出的功能,使應(yīng)用程序能夠在關(guān)閉時(shí)正常處理完所有當(dāng)前請求,避免請求被中斷導(dǎo)致數(shù)據(jù)丟失或不一致等問題,本文將全面介紹如何在 Spring Boot 應(yīng)用程序中實(shí)現(xiàn)優(yōu)雅退出,感興趣的朋友跟隨小編一起看看吧
    2024-03-03
  • Java讀取文件方法匯總

    Java讀取文件方法匯總

    這篇文章主要為大家詳細(xì)介紹了Java讀取文件方法,按字節(jié)讀取文件內(nèi)容、按字符讀取文件內(nèi)容、隨機(jī)讀取文件內(nèi)容等,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2016-12-12

最新評論

高清| 寿宁县| 天门市| 清镇市| 获嘉县| 乐清市| 博野县| 盐源县| 运城市| 绥宁县| 扶风县| 洪湖市| 伊宁市| 鄱阳县| 六枝特区| 九龙县| 新巴尔虎左旗| 万宁市| 达拉特旗| 高安市| 关岭| 磐安县| 盐源县| 墨玉县| 义乌市| 聊城市| 宜宾市| 大荔县| 淳化县| 罗甸县| 昭通市| 恩平市| 东安县| 黔江区| 许昌县| 章丘市| 蒙山县| 遂溪县| 湖口县| 磴口县| 离岛区|