詳解Spring?@Lazy注解為什么能破解死循環(huán)
以下內(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使用poi實(shí)現(xiàn)excel的導(dǎo)入操作指南
使用Apache Poi是一種流行且廣泛使用的方式,可以幫助開發(fā)人員直接從Java代碼中讀取、寫入和處理Excel文件,因此在這篇文章我們將著重介紹如何實(shí)現(xiàn)excel的導(dǎo)入,感興趣的朋友可以跟著小編一起來學(xué)習(xí)2023-06-06SpringBoot中配置nacos的方法實(shí)現(xiàn)
本文主要介紹了SpringBoot中配置nacos的方法實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-08-08Java使用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-03java 中使用maven shade plugin 打可執(zhí)行Jar包
這篇文章主要介紹了java 中使用maven shade plugin 打可執(zhí)行Jar包的相關(guān)資料,需要的朋友可以參考下2017-05-05