免费视频淫片aa毛片_日韩高清在线亚洲专区vr_日韩大片免费观看视频播放_亚洲欧美国产精品完整版

打開APP
userphoto
未登錄

開通VIP,暢享免費(fèi)電子書等14項(xiàng)超值服

開通VIP
別再說 Spring AOP 默認(rèn)用的是 JDK 動(dòng)態(tài)代理

這篇是 Spring 面試題的第三篇了,我們來盤一下有關(guān) AOP 的面試題。

關(guān)于標(biāo)題的回答,在第二部分,別急。

話不多說,發(fā)車!

說下 AOP

AOP,Aspect Oriented Programming,面向切面編程。

將一些通用的邏輯集中實(shí)現(xiàn),然后通過 AOP 進(jìn)行邏輯的切入,減少了零散的碎片化代碼,提高了系統(tǒng)的可維護(hù)性。

具體是含義可以理解為:通過代理的方式,在調(diào)用想要的對象方法時(shí)候,進(jìn)行攔截處理,執(zhí)行切入的邏輯,然后再調(diào)用真正的方法實(shí)現(xiàn)。

例如,你實(shí)現(xiàn)了一個(gè) A 對象,里面有  addUser 方法,此時(shí)你需要記錄該方法的調(diào)用次數(shù)。

那么你就可以搞個(gè)代理對象,這個(gè)代理對象也提供了 addUser 方法,最終你調(diào)用的是代理對象的 addUser ,在這個(gè)代理對象內(nèi)部填充記錄調(diào)用次數(shù)的邏輯,最終的效果就類似下面代碼:

class A代理 {
    A a;// 被代理的 A
   void addUser(User user) {
     count();// 計(jì)數(shù)
     a.addUser(user);
   }
}
最終使用的是:
A代理.addUser(user);

這就叫做面向切面編程,當(dāng)然具體的代理的代碼不是像上面這樣寫死的,而是動(dòng)態(tài)切入

實(shí)現(xiàn)上代理大體上可以分為:動(dòng)態(tài)代理靜態(tài)代理。

  • 動(dòng)態(tài)代理,即在運(yùn)行時(shí)將切面的邏輯進(jìn)去,按照上面的邏輯就是你實(shí)現(xiàn) A 類,然后定義要代理的切入點(diǎn)和切面的實(shí)現(xiàn),程序會自動(dòng)在運(yùn)行時(shí)生成類似上面的代理類。
  • 靜態(tài)代理,在編譯時(shí)或者類加載時(shí)進(jìn)行切面的織入,典型的 AspectJ 就是靜態(tài)代理。

Spring AOP默認(rèn)用的是什么動(dòng)態(tài)代理,兩者的區(qū)別

Spring AOP 的動(dòng)態(tài)代理實(shí)現(xiàn)分別是:JDK 動(dòng)態(tài)代理與 CGLIB。

默認(rèn)的實(shí)現(xiàn)是 JDK 動(dòng)態(tài)代理。

ok,這個(gè)問題沒毛?。▽?shí)際應(yīng)用來說其實(shí)不太準(zhǔn)確),然后面試官接著問那你平時(shí)有調(diào)試過嗎,確定你得到的代理對象是 JDK 動(dòng)態(tài)代理實(shí)現(xiàn)的?

然后你信誓旦旦的說,對,我們都實(shí)現(xiàn)接口的,所以是 JDK 動(dòng)態(tài)代理。

然而你簡歷上寫著項(xiàng)目使用的框架是 SpringBoot,我問你 SpringBoot 是什么版本,你說2.x。

然后我就可以推斷,你沒看過,你大概率僅僅只是網(wǎng)上看了相關(guān)的面試題。

要注意上面說的默認(rèn)實(shí)現(xiàn)是 Spring Framework (最新版我沒去驗(yàn)證),而 SpringBoot 2.x 版本已經(jīng)默認(rèn)改成了 CGLIB。

而我們現(xiàn)在公司大部分使用的都是 SpringBoot 2.x 版本,所以你要說默認(rèn) JDK 動(dòng)態(tài)代理也沒錯(cuò),但是不符合你平日使用的情況,對吧?

如果你調(diào)試過,或者看過調(diào)用棧,你肯定能發(fā)現(xiàn)默認(rèn)用的是 CGLIB(當(dāng)然你要是沒用 SpringBoot 當(dāng)我沒說哈):

市面上大部分面試題答案寫的就是 JDK 動(dòng)態(tài)代理,是沒錯(cuò),Spring 官網(wǎng)都這樣寫的。

但是咱們現(xiàn)在不都是用 SpringBoot 了嘛,所以這其實(shí)不符合我們當(dāng)下使用的情況。

因此,面試時(shí)候不要只說 Spring AOP 默認(rèn)用的是 JDK 動(dòng)態(tài)代理,把 SpringBoot 也提一嘴,這不就是讓面試官刮目一看嘛(不過指不定面試官也不知道~)

如果要修改 SpringBoot 使用 JDK 動(dòng)態(tài)代理,那么設(shè)置 spring.aop.proxy-target-class=false

如果你提了這個(gè),那面試官肯定會追問:

那為什么要改成默認(rèn)用 CGLIB?

嘿嘿,答案我也為你準(zhǔn)備好了,我們來看看:

大佬說 JDK 動(dòng)態(tài)代理要求接口,所以沒有接口的話會有報(bào)錯(cuò),很令人討厭,并且讓 CGLIB 作為默認(rèn)也沒什么副作用,特別是 CGLIB 已經(jīng)被重新打包為 Spring 的一部分了,所以就默認(rèn) CGLIB 。

好吧,其實(shí)也沒有什么很特殊的含義,就是效果沒差多少,還少報(bào)錯(cuò),方便咯。

詳細(xì)issue 鏈接:https://github.com/spring-projects/spring-boot/issues/5423

JDK 動(dòng)態(tài)代理

JDK 動(dòng)態(tài)代理是基于接口的,也就是被代理的類一定要實(shí)現(xiàn)了某個(gè)接口,否則無法被代理。

主要實(shí)現(xiàn)原理就是:

  1. 首先通過實(shí)現(xiàn)一個(gè) InvocationHandler 接口得到一個(gè)切面類。
  2. 然后利用 Proxy 糅合目標(biāo)類的類加載器、接口和切面類得到一個(gè)代理類。
  3. 代理類的邏輯就是執(zhí)行切入邏輯,把所有接口方法的調(diào)用轉(zhuǎn)發(fā)到 InvocationHandler 的 invoke() 方法上,然后根據(jù)反射調(diào)用目標(biāo)類的方法。

我們再深入一點(diǎn)點(diǎn)了解下原理實(shí)現(xiàn)。

如果你反編譯的話,你能看到生成的代理類是會先在靜態(tài)塊中通過反射把所有方法都拿到存在靜態(tài)變量中,(我盲寫了一下)大致長這樣:

上面就是把 getUserInfo 方法緩存了,然后在調(diào)用代理類的 getUserInfo 的時(shí)候,會調(diào)用你之前實(shí)現(xiàn)的 InvocationHandler 里面的 invoke。

這樣就執(zhí)行到切入的邏輯了,且最終執(zhí)行了被代理類的  getUserInfo 方法。

就是中間商攔了一道咯,道理就是這個(gè)道理。

CGLIB

在 Spring 里面,如果被代理的類沒有實(shí)現(xiàn)接口,那么就用 CGLIB 來完成動(dòng)態(tài)代理。

CGLIB 是基于ASM 字節(jié)碼生成工具,它是通過繼承的方式來實(shí)現(xiàn)代理類,所以要注意 final 方法,這種方法無法被繼承。

簡單理解下,就是生成代理類的子類,如何生成呢?

通過字節(jié)碼技術(shù)動(dòng)態(tài)拼接成一個(gè)子類,在其中織入切面的邏輯

使用例子:

Enhancer en = new Enhancer();
//2.設(shè)置父類,也就是代理目標(biāo)類,上面提到了它是通過生成子類的方式
en.setSuperclass(target.getClass());
//3.設(shè)置回調(diào)函數(shù),這個(gè)this其實(shí)就是代理邏輯實(shí)現(xiàn)類,也就是切面,可以理解為JDK 動(dòng)態(tài)代理的handler
en.setCallback(this);
//4.創(chuàng)建代理對象,也就是目標(biāo)類的子類了。
return en.create();

JDK 動(dòng)態(tài)代理和 CGLIB 兩者經(jīng)常還可能被面試官問性能對比,所以咱們也列一下(以下內(nèi)容取自:haiq的博客):

  • jdk6 下,在運(yùn)行次數(shù)較少的情況下,jdk動(dòng)態(tài)代理與 cglib 差距不明顯,甚至更快一些;而當(dāng)調(diào)用次數(shù)增加之后, cglib 表現(xiàn)稍微更快一些
  • jdk7 下,情況發(fā)生了逆轉(zhuǎn)!在運(yùn)行次數(shù)較少(1,000,000)的情況下,jdk動(dòng)態(tài)代理比 cglib 快了差不多30%;而當(dāng)調(diào)用次數(shù)增加之后(50,000,000), 動(dòng)態(tài)代理比 cglib 快了接近1倍
  • jdk8 表現(xiàn)和 jdk7 基本一致

我沒試過,有興趣的同學(xué)可以自己實(shí)驗(yàn)一下。

能說說攔截鏈的實(shí)現(xiàn)嗎?

我們都知道 Spring AOP 提供了多種攔截點(diǎn),便捷我們對 AOP 的使用,比如 @Before、@After、@AfterReturning、@AfterThrowing 等等。

方便我們在目標(biāo)方法執(zhí)行前、后、拋錯(cuò)等地方進(jìn)行一些邏輯的切入。

那 Spring 具體是如何鏈起這些調(diào)用順序的呢?

這就是攔截鏈干的事,實(shí)際上這些注解都對應(yīng)著不同的 interceptor 實(shí)現(xiàn)。

然后 Spring 會利用一個(gè)集合把所有類型的 interceptor 組合起來,我在代碼里用了 @Before、@After、@AfterReturning、@AfterThrowing這幾個(gè)注解。

于是集合里就有了這些 interceptor(多了一個(gè) expose...等下解釋),這是由 Spring 掃描到注解自動(dòng)加進(jìn)來的:

然后通過一個(gè)對象 CglibMethodInvocation 將這個(gè)集合封裝起來,緊接著調(diào)用這個(gè)對象的 proceed 方法,可看到這個(gè)集合 chain 被傳入了。

我們來看下 CglibMethodInvocation#proceed 方法邏輯。

要注意,這里就開始遞歸套娃了,核心調(diào)用邏輯就在這里:

可以看到有個(gè) currentInterceptorIndex 變量,通過遞歸,每次新增這索引值,來逐得到下一個(gè) interceptor 。

并且每次都傳入當(dāng)前對象并調(diào)用  interceptor#invoke ,這樣就實(shí)現(xiàn)了攔截鏈的調(diào)用,所以這是個(gè)遞歸。

我們拿集合里面的 MethodBeforeAdviceInterceptor 來舉例看下,這個(gè)是目標(biāo)方法執(zhí)行的前置攔截,我們看下它的 invoke 實(shí)現(xiàn),有更直觀的認(rèn)識:

invoke 的實(shí)現(xiàn)是先執(zhí)行切入的前置邏輯,然后再繼續(xù)調(diào)用 CglibMethodInvocation#proceed(也就是mi.proceed),進(jìn)行下一個(gè) interceptor 的調(diào)用。

總結(jié)下

Spring 根據(jù) @Before、@After、@AfterReturning、@AfterThrowing 這些注解。

往集合里面加入對應(yīng)的 Spring 提供的  MethodInterceptor 實(shí)現(xiàn)。

比如上面的 MethodBeforeAdviceInterceptor ,如果你沒用 @Before,集合里就沒有 MethodBeforeAdviceInterceptor 。

然后通過一個(gè)對象 CglibMethodInvocation 將這個(gè)集合封裝起來,緊接著調(diào)用這個(gè)對象的 proceed 方法。

具體是利用 currentInterceptorIndex 下標(biāo),利用遞歸順序地執(zhí)行集合里面的 MethodInterceptor ,這樣就完成了攔截鏈的調(diào)用。

我截個(gè)調(diào)用鏈的堆棧截圖,可以很直觀地看到調(diào)用的順序(從下往上看):

是吧,是按照順序一個(gè)一個(gè)往后執(zhí)行,然后再一個(gè)一個(gè)返回,就是遞歸唄。

然后我再解釋下上面的 chain 集合我們看到第一個(gè)索引位置的 ExposeInvocationInterceptor 。

這個(gè) Interceptor 作為第一個(gè)被調(diào)用,實(shí)際上就是將創(chuàng)建的 CglibMethodInvocation 這個(gè)對象存入 threadlocal 中,方便后面 Interceptor 調(diào)用的時(shí)候能得到這個(gè)對象,進(jìn)行一些調(diào)用。

從名字就能看出 expose:暴露。

ok,更多細(xì)節(jié)還是得自己看源碼的,應(yīng)付面試了解到這個(gè)程度差不多的,上面幾個(gè)關(guān)鍵點(diǎn)一拋,這個(gè)題絕對穩(wěn)了!

Spring AOP 和 AspectJ 有什么區(qū)別

從上面的題目我們已經(jīng)知道,兩者分別是動(dòng)態(tài)代理和靜態(tài)代理的區(qū)別。

Spring AOP 是動(dòng)態(tài)代理,AspectJ 是靜態(tài)代理。

從一個(gè)是運(yùn)行時(shí)織入,一個(gè)在編譯時(shí)織入,我們稍微一想到就能知道,編譯時(shí)就準(zhǔn)備完畢,那么在調(diào)用時(shí)候沒有額外的織入開銷,性能更好些。

且 AspectJ 提供完整的 AOP 解決方案,像 Spring AOP 只支持方法級別的織入,而 AspectJ 支持字段、方法、構(gòu)造函數(shù)等等,所以它更加強(qiáng)大,當(dāng)然也更加復(fù)雜。


本站僅提供存儲服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊舉報(bào)。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
Spring高級程序設(shè)計(jì) 5 Spring AOP基礎(chǔ)
spring框架-認(rèn)識AOP(三)
Spring AOP介紹及源碼分析
Spring AOP中JDK和CGLib動(dòng)態(tài)代理哪個(gè)更快?
Spring03——有關(guān)于 Spring AOP 的總結(jié)
Java實(shí)現(xiàn)AOP的幾種方式
更多類似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長圖 關(guān)注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服