乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

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

       昵稱10087950 2022-06-16 發(fā)布于江蘇

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

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

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

      說(shuō)下 AOP

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

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

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

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

      那么你就可以搞個(gè)代理對(duì)象,這個(gè)代理對(duì)象也提供了 addUser 方法,最終你調(diào)用的是代理對(duì)象的 addUser ,在這個(gè)代理對(duì)象內(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)然具體的代理的代碼不是像上面這樣寫(xiě)死的,而是動(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),程序會(huì)自動(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è)問(wèn)題沒(méi)毛病(對(duì)實(shí)際應(yīng)用來(lái)說(shuō)其實(shí)不太準(zhǔn)確),然后面試官接著問(wèn)那你平時(shí)有調(diào)試過(guò)嗎,確定你得到的代理對(duì)象是 JDK 動(dòng)態(tài)代理實(shí)現(xiàn)的?

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

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

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

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

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

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

      圖片

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

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

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

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

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

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

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

      圖片
      圖片

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

      好吧,其實(shí)也沒(méi)有什么很特殊的含義,就是效果沒(méi)差多少,還少報(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è)接口,否則無(wú)法被代理。

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

      1. 首先通過(guò)實(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)類的方法。

      圖片我們?cè)偕钊胍稽c(diǎn)點(diǎn)了解下原理實(shí)現(xiàn)。

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

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

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

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

      CGLIB

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

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

      簡(jiǎn)單理解下,就是生成代理類的子類,如何生成呢?

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

      使用例子:

      Enhancer en = new Enhancer();
      //2.設(shè)置父類,也就是代理目標(biāo)類,上面提到了它是通過(guò)生成子類的方式
      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)建代理對(duì)象,也就是目標(biāo)類的子類了。
      return en.create();

      JDK 動(dòng)態(tài)代理和 CGLIB 兩者經(jīng)常還可能被面試官問(wèn)性能對(duì)比,所以咱們也列一下(以下內(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 基本一致

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

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

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

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

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

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

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

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

      圖片

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

      圖片

      我們來(lái)看下 CglibMethodInvocation#proceed 方法邏輯。

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

      圖片

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

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

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

      圖片

      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 這些注解。

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

      比如上面的 MethodBeforeAdviceInterceptor ,如果你沒(méi)用 @Before,集合里就沒(méi)有 MethodBeforeAdviceInterceptor 。

      然后通過(guò)一個(gè)對(duì)象 CglibMethodInvocation 將這個(gè)集合封裝起來(lái),緊接著調(diào)用這個(gè)對(duì)象的 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è)對(duì)象存入 threadlocal 中,方便后面 Interceptor 調(diào)用的時(shí)候能得到這個(gè)對(duì)象,進(jìn)行一些調(diào)用。

      圖片

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

      ok,更多細(xì)節(jié)還是得自己看源碼的,應(yīng)付面試了解到這個(gè)程度差不多的,上面幾個(gè)關(guān)鍵點(diǎn)一拋,這個(gè)題絕對(duì)穩(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í)候沒(méi)有額外的織入開(kāi)銷,性能更好些。

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


        本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
        轉(zhuǎn)藏 分享 獻(xiàn)花(0

        0條評(píng)論

        發(fā)表

        請(qǐng)遵守用戶 評(píng)論公約

        類似文章 更多