1. <span id="abflz"><sup id="abflz"></sup></span>
    2. 為了賬號安全,請及時綁定郵箱和手機立即綁定

      【好好面試】學完Aop,你卻連動態代理原理都不懂?

      2019.08.05 00:12 440瀏覽

      【干貨點】 此處是【好好面試】系列文的第12篇文章。文章目標主要是通過原理剖析的方式解答Aop動態代理的面試熱點問題,通過一步步提出問題和了解原理的方式,我們可以記得更深更牢,進而解決被面試官卡住喉嚨的情況。
      問題如下

      • SpringBoot默認代理類型是什么
      • 為什么不用靜態代理
      • JDK動態代理原理
      • CGLIB動態代理原理
      • JDK動態代理和CGLIB動態代理的區別
      • 為什么CGLIB不能像JDK代理那樣,直接使用反射觸發目標函數
      • 為什么CGLIB代理可以直接對類進行代理,而JDK代理卻一定要實現接口

      大前提

      該文章是必須要懂的SpringAop系列的最后一篇文章,第一篇文章是你必須要懂的Spring-Aop之應用篇,第二篇文章是你必須要懂的Spring-Aop之源碼跟蹤分析Aop,最后一篇文章我們將會揭露Aop的原理,也就是動態代理。
      希望看完這最后一篇文章,大家對SpringAop都有一個全面的掌握,不管面試官如何問,都可以屌回去 ━━( ̄ー ̄*|||━━

      溫馨提示:前面沒看的最好點擊瀏覽下!!!

      說個坑爹的事

      大家都知道,我有個習慣,在動手寫一篇文章之前會先將該文章相關的資料仔細琢磨一遍,然后再結合源碼再調試一遍,結果,說好的


      看源碼也確實是

      源碼確實有進行了是否是接口的判斷,但是問題來了,我調試的時候發現無論代理類是否有接口,最終都會被強制使用CGLIB代理,沒辦法,只能翻看SpringBoot的相關文檔,最終發現原來SpringBoot從2.0開始就默認使用Cglib代理了,好家伙,怪不得我調試半天找不到原因。

      那么如何解決呢?肯定是通過配置啦,按照如下配置即可

      在application.properties文件中配置 spring.aop.proxy-target-class=false 即可。

      【劃重點】 曾經遇見過面試官問,SpringBoot默認代理類型是什么?看完該篇文章,我們就可以果斷的回答是Cglib代理了。通過調試代碼發現的規則,我想我這輩子都不會忘記這個默認規則。

      動態代理原理剖析

      什么是代理

      簡單來說,就是在運行的時候為目標類動態生成代理類,而在操作的時候都是操作代理類,代理模式有個顯而易見的好處,那便是可以在不改變對象方法的情況下對方法進行增強。試想下,我們在你必須要懂的Spring-Aop之應用篇有提到使用Aop來做權限認證,如果不用Aop,那么我們就必須要為所有需要權限認證的方法都加上權限認證代碼,聽起來就覺得蛋疼,你覺得對不對?

      為什么不用靜態代理

      靜態代理類不是說不可以用,如果只有一個類需要被代理,那么自然可以用,如 這是在你必須要懂的Spring-Aop之應用篇使用的一個例子類,該類的作用只是打印出我要買東西。
      3 代理類如下
      4 可以看到這個BuyProxy代理類只是塞了一個IBuyServcie接口進行,而且自身也實現了接口IBuyService,而在buyItem方法被調用的時候會先做自己的操作,再調用塞進去的接口的buyItem方法。 測試類很簡單,如下
      5 運行后很自然而然的打印出
      6

      靜態代理就是簡單,但是弊端也很明顯,如果有多個類都需要同樣的代理,都實現了同樣的接口,那么如果使用靜態代理的話,我們就要構造多個Proxy類,就會造成類爆炸。 而使用了Aop后,也就是動態代理后,便可以一次性解決該問題了,具體可以看你必須要懂的Spring-Aop之應用篇中的操作方法。

      JDK動態代理原理

      這里給出一個JDK動態代理的demo 首先給出一個簡單的業務類,Hello類和接口 78 真正實現了類的代理功能的其實就是這個實現了接口InvocationHandler的JdkProxy類 9 我們可以看到其中必須實現的方法是invoke,可以看到invoke方法的參數帶有Method對象,這個就是我們的目標Method,現在我們的目的就是要在這個Method在被調用前后實現我們的業務,可以看到在method.invoke反調前后實現了before和after業務。 這里再給出一個Main測試類,作用是取得Hello的代理類,然后調用其中的say方法。 10 運行結果如下 11

      原理很簡單 在JdkProxyMain中hello調用say的時候,由于Hello已經被“代理”了,所以在調用say函數的時候其實是調用JdkProxy類中的invoke函數,而在invoke函數中先是實現了before函數才實現Object result = method.invoke(target, args),這一句其實是調用say函數,而后才實現after函數,于是這樣就可以不必在改動目標類的前提下實現代理了,并且不會像靜態代理那樣導致類爆炸。

      CGLIB動態代理原理 先給出一個Cglib動態代理的demo 13

      【思考題一】為什么CGLIB代理可以直接對類進行代理,而JDK代理卻一定要實現接口呢?答案見問末!!! 核心類是實現了MethodInterceptor的CGlibProxy類

      14

      可以看到其中實現了方法intercept,先是在目標函數被調用前實現自己的業務,比如before()和after(),之后再通過 proxy.invokeSuper(obj, args) 觸發目標函數。

      【思考題二】為什么這里不像JDK代理那樣,直接使用反射[method.invoke(target, args)]觸發目標函數?答案見問末!!! 最后給出入口類

      15 最后給出運行類,運行類如下 15 可以看到運行結果 16

      原理很簡單 在CglibProxyMain中hello調用say的時候,由于Hello已經被“代理”了,所以在調用say函數的時候其實是調用CGlibProxy類中的intercept函數。

      總結

      動態代理的相關原理已經講解完畢,接下來讓我們回答以下幾個思考題。
      「思考解惑一」為什么CGLIB代理可以直接對類進行代理,而JDK代理卻一定要實現接口呢?
      可以從我們上面的例子看出,在JdkProxy類中取得代理類的方式是 (T)Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(), this) 而在CGlibProxy類中取得代理類的方式是 (T) Enhancer.create(cls, this) 兩種取得代理類的方式不同,導致了一個需要實現接口,一個不需要。

      「思考解惑二」為什么這里不像JDK代理那樣,直接使用反射[method.invoke(target, args)]觸發目標函數?
      首先要進行反射觸發函數,要取得對應的method,以及該method所屬對象,也就是target,再次是args方法參數,而我們看下調試界面 17

      可以從界面看到,目標對象obj并不是Hello對象,二是被CGLIB代理過的對象,因此無法像JDK代理那樣直接通過反射搞定。

      點擊查看更多內容

      本文首次發布于慕課網 ,轉載請注明出處,謝謝合作

      2人點贊

      若覺得本文不錯,就分享一下吧!

      評論

      相關文章推薦

      正在加載中
      意見反饋 邀請有獎 幫助中心 APP下載
      官方微信

      舉報

      0/150
      提交
      取消
      成人在线av