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

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

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

大前提

該文章是必需要懂的SpringAop系列的最後一篇文章,第一篇文章是你必需要懂的Spring-Aop之應用篇,第二篇文章是你必需要懂的Spring-Aop之源碼跟蹤分析Aop,最後一篇文章咱們將會揭露Aop的原理,也就是動態代理。但願看完這最後一篇文章,你們對SpringAop都有一個全面的掌握,無論面試官如何問,均可以屌回去 ━━( ̄ー ̄*|||━━spring

舒適提示:前面沒看的最好點擊瀏覽下!!!app

說個坑爹的事

你們都知道,我有個習慣,在動手寫一篇文章以前會先將該文章相關的資料仔細琢磨一遍,而後再結合源碼再調試一遍,結果,說好的函數

看源碼也確實是測試

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

那麼如何解決呢?確定是經過配置啦,按照以下配置便可3d

在application.properties文件中配置 spring.aop.proxy-target-class=false代理

便可。調試

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

動態代理原理剖析

什麼是代理

簡單來講,就是在運行的時候爲目標類動態生成代理類,而在操做的時候都是操做代理類,代理模式有個顯而易見的好處,那即是能夠在不改變對象方法的狀況下對方法進行加強。試想下,咱們在你必需要懂的Spring-Aop之應用篇有提到使用Aop來作權限認證,若是不用Aop,那麼咱們就必需要爲全部須要權限認證的方法都加上權限認證代碼,聽起來就以爲蛋疼,你以爲對不對?

爲何不用靜態代理

靜態代理類不是說不能夠用,若是隻有一個類須要被代理,那麼天然能夠用,如 這是在你必需要懂的Spring-Aop之應用篇使用的一個例子類,該類的做用只是打印出我要買東西。

3

代理類以下

4

能夠看到這個BuyProxy代理類只是塞了一個IBuyServcie接口進行,並且自身也實現了接口IBuyService,而在buyItem方法被調用的時候會先作本身的操做,再調用塞進去的接口的buyItem方法。 測試類很簡單,以下

5

運行後很天然而然的打印出

6

靜態代理就是簡單,可是弊端也很明顯,若是有多個類都須要一樣的代理,都實現了一樣的接口,那麼若是使用靜態代理的話,咱們就要構造多個Proxy類,就會形成類爆炸。 而使用了Aop後,也就是動態代理後,即可以一次性解決該問題了,具體能夠看你必需要懂的Spring-Aop之應用篇中的操做方法。

JDK動態代理原理

這裏給出一個JDK動態代理的demo 首先給出一個簡單的業務類,Hello類和接口

7

8

真正實現了類的代理功能的其實就是這個實現了接口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代理那樣直接經過反射搞定。

logo
相關文章
相關標籤/搜索