欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

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

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

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

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

上篇文章第一小節(jié)的案例為例,在構(gòu)造方法中互相注入對方的 Bean,此時完全就是一個死循環(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)目,就不會報錯了。這樣看起來問題解決了,但是其實(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 能把一個死結(jié)給解開,就是因?yàn)?AService 和 BService 各自注入的 Bean 都不是原始的 Bean,都是一個代理的 Bean,AService 中注入的 BService 是一個代理對象,同理,BService 中注入的 AService 也是一個代理對象。

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

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

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

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

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

對于含有 @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 注解是通過建立一個中間代理層,來破解循環(huán)依賴的。

2. 原理分析

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

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

1.在創(chuàng)建 Bean 的時候,原始 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,這個方法又會進(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)邏輯就是在這個方法中進(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;
?}
}

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

在最后一個 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);
}

大家看一下,這個方法首先會調(diào)用 isLazy 去判斷一下是否需要延遲加載,如果需要,則調(diào)用 buildLazyResolutionProxy 方法構(gòu)建一個延遲加載的對象;如果不需要,則直接返回一個 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;
}

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

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

相關(guān)文章

  • java selenium元素定位大全

    java selenium元素定位大全

    本文主要介紹java selenium元素定位,這里整理了selenium元素定位的相關(guān)資料,有興趣的小伙伴可以參考下
    2016-08-08
  • mybatis中的緩存問題解析

    mybatis中的緩存問題解析

    本篇文章主要介紹了mybatis中的緩存問題解析,詳細(xì)的介紹了關(guān)于mybatis的一級緩存和二級緩存,具有一定的參考價值,有興趣的可以了解一下。
    2017-04-04
  • Java使用poi實(shí)現(xiàn)excel的導(dǎo)入操作指南

    Java使用poi實(shí)現(xiàn)excel的導(dǎo)入操作指南

    使用Apache Poi是一種流行且廣泛使用的方式,可以幫助開發(fā)人員直接從Java代碼中讀取、寫入和處理Excel文件,因此在這篇文章我們將著重介紹如何實(shí)現(xiàn)excel的導(dǎo)入,感興趣的朋友可以跟著小編一起來學(xué)習(xí)
    2023-06-06
  • Java?詳細(xì)講解用堆解決Top-k問題

    Java?詳細(xì)講解用堆解決Top-k問題

    TopK問題即在N個數(shù)中找出最大的前K個,這篇文章將詳細(xì)講解如何利用小根堆的方法解決TopK問題,文中代碼具有一定參考價值,快跟隨小編一起學(xué)習(xí)一下吧
    2022-04-04
  • SpringMVC超詳細(xì)介紹自定義攔截器

    SpringMVC超詳細(xì)介紹自定義攔截器

    Spring?MVC?的攔截器(Interceptor)與?Java?Servlet?的過濾器(Filter)類似,它主要用于攔截用戶的請求并做相應(yīng)的處理,通常應(yīng)用在權(quán)限驗(yàn)證、記錄請求信息的日志、判斷用戶是否登錄等功能上。本文將代碼演示和文字描述詳解攔截器的原理與使用
    2022-06-06
  • SpringBoot中配置nacos的方法實(shí)現(xiàn)

    SpringBoot中配置nacos的方法實(shí)現(xiàn)

    本文主要介紹了SpringBoot中配置nacos的方法實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2024-08-08
  • Java使用Soap方式調(diào)用WebService接口代碼示例

    Java使用Soap方式調(diào)用WebService接口代碼示例

    Java調(diào)用WebService接口是指通過Java語言來訪問并與WebService進(jìn)行交互,WebService是一種基于Web的服務(wù)架構(gòu),它通過標(biāo)準(zhǔn)的XML和HTTP協(xié)議來提供服務(wù),這篇文章主要給大家介紹了關(guān)于Java使用Soap方式調(diào)用WebService接口的相關(guān)資料,需要的朋友可以參考下
    2024-03-03
  • java 中使用maven shade plugin 打可執(zhí)行Jar包

    java 中使用maven shade plugin 打可執(zhí)行Jar包

    這篇文章主要介紹了java 中使用maven shade plugin 打可執(zhí)行Jar包的相關(guān)資料,需要的朋友可以參考下
    2017-05-05
  • java HashMap詳解及實(shí)例代碼

    java HashMap詳解及實(shí)例代碼

    這篇文章主要介紹了java HashMap詳解及實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下
    2017-01-01
  • Java語言打印九九乘法表

    Java語言打印九九乘法表

    這篇文章主要為大家詳細(xì)介紹了Java語言打印九九乘法表的相關(guān)代碼,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2016-06-06

最新評論