【乾貨點】 此處是【好好面試】系列文的第12篇文章。 文章目標主要是經過原理剖析的方式解答Aop動態代理的面試熱點問題,經過一步步提出問題和了解原理的方式,咱們能夠記得更深更牢,進而解決被面試官卡住喉嚨的狀況。 問題以下面試
該文章是必需要懂的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之應用篇使用的一個例子類,該類的做用只是打印出我要買東西。
代理類以下
能夠看到這個BuyProxy代理類只是塞了一個IBuyServcie接口進行,並且自身也實現了接口IBuyService,而在buyItem方法被調用的時候會先作本身的操做,再調用塞進去的接口的buyItem方法。 測試類很簡單,以下
運行後很天然而然的打印出
靜態代理就是簡單,可是弊端也很明顯,若是有多個類都須要一樣的代理,都實現了一樣的接口,那麼若是使用靜態代理的話,咱們就要構造多個Proxy類,就會形成類爆炸。 而使用了Aop後,也就是動態代理後,即可以一次性解決該問題了,具體能夠看你必需要懂的Spring-Aop之應用篇中的操做方法。
這裏給出一個JDK動態代理的demo 首先給出一個簡單的業務類,Hello類和接口
真正實現了類的代理功能的其實就是這個實現了接口InvocationHandler的JdkProxy類
咱們能夠看到其中必須實現的方法是invoke,能夠看到invoke方法的參數帶有Method對象,這個就是咱們的目標Method,如今咱們的目的就是要在這個Method在被調用先後實現咱們的業務,能夠看到在method.invoke反調先後實現了before和after業務。
這裏再給出一個Main測試類,做用是取得Hello的代理類,而後調用其中的say方法。
運行結果以下
原理很簡單 在JdkProxyMain中hello調用say的時候,因爲Hello已經被「代理」了,因此在調用say函數的時候實際上是調用JdkProxy類中的invoke函數,而在invoke函數中先是實現了before函數才實現Object result = method.invoke(target, args),這一句實際上是調用say函數,然後才實現after函數,因而這樣就能夠沒必要在改動目標類的前提下實現代理了,而且不會像靜態代理那樣致使類爆炸。
先給出一個Cglib動態代理的demo
【思考題一】爲何CGLIB代理能夠直接對類進行代理,而JDK代理卻必定要實現接口呢?答案見問末!!!
核心類是實現了MethodInterceptor的CGlibProxy類
能夠看到其中實現了方法intercept,先是在目標函數被調用前實現本身的業務,好比before()和after(),以後再經過 proxy.invokeSuper(obj, args) 觸發目標函數。
【思考題二】爲何這裏不像JDK代理那樣,直接使用反射[method.invoke(target, args)]觸發目標函數?答案見問末!!!
最後給出入口類
最後給出運行類,運行類以下
能夠看到運行結果
原理很簡單 在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方法參數,而咱們看下調試界面
能夠從界面看到,目標對象obj並非Hello對象,二是被CGLIB代理過的對象,所以沒法像JDK代理那樣直接經過反射搞定。