https://mbd.baidu.com/newspage/data/landingsuper?context=%7B%22nid%22%3A%22news_9403056301388627935%22%7D&n_type=0&p_from=1面試
咱們知道,Spring 中 AOP 是一大核心技術,也是面試中常常會被問到的問題,最近我在網上也看到不少面試題,其中和 Spring AOP 相關的就有很多,這篇文章主要來總結下相關的技術點,但願對你們有用。編程
幾個常見的問題設計模式
針對這一塊的東西,通常下面幾個問題面試官問的比較多:ide
* Spring AOP用的是哪一種設計模式?工具
* 談談你對代理模式的理解?性能
* 靜態代理和動態代理有什麼區別?測試
* 如何實現動態代理?ui
* Spring AOP中用的是哪一種代理技術?this
若是這些問題都能回答的很流暢的話,說明對代理這一塊的基本知識有必定的瞭解了。由於咱們在實際開發中,寫業務代碼會更多,因此這一塊的東西,大部分人可能知道個一二,可是若是讓他們頗有條理的表達出來,可能就不那麼容易了。spa
什麼是 Spring AOP?
通常面試官問到這個問題,面試者基本上都會回答:AOP 就是面向切面編程。其實這真的是句廢話,這麼回答真的沒有任何意義。
或許你能夠給面試官舉個例子:歌星都有好多助理,歌星最重要的一件事就是唱歌,其餘事他不用關注,好比唱歌前可能須要和其餘人談合做,還要佈置場地,唱歌后還要收錢等等,這些通通交給他對應的助理去作。
也許哪一天,這個歌星作慈善,免費唱歌了,不收錢了,那麼就能夠把收錢這個助力給辭退了。這就是 AOP,每一個人各司其職,靈活組合,達到一種可配置的、可插拔的程序結構。AOP 的實現原理就是代理模式。
在程序中也是如此,經過代理,能夠詳細控制訪問某個或者某類對象的方法,在調用這個方法前作前置處理,調用這個方法後作後置處理。
什麼是代理模式?
代理模式的核心做用就是經過代理,控制對對象的訪問。它的設計思路是:定義一個抽象角色,讓代理角色和真實角色分別去實現它。
真實角色:實現抽象角色,定義真實角色所要實現的業務邏輯,供代理角色調用。它只關注真正的業務邏輯,好比歌星唱歌。
代理角色:實現抽象角色,是真實角色的代理,經過真實角色的業務邏輯方法來實現抽象方法,並在先後能夠附加本身的操做,好比談合同,佈置場地,收錢等等。
這就是代理模式的設計思路。代理模式分爲靜態代理和動態代理。靜態代理是咱們本身建立一個代理類,而動態代理是程序自動幫咱們生成一個代理,咱們就不用管了。下面我詳細介紹一下這兩種代理模式。
靜態代理模式
就舉明星唱歌這個例子,根據上面提供的設計思路,首先咱們須要建立明星這個抽象角色:
/*** 明星接口類 * @author shengwu ni * @date 2018-12-07*/publicinterfaceStar {/** * 唱歌方法 */voidsing();}
靜態代理須要建立真實角色和代理角色,分別實現唱歌這個接口,真實角色很簡單,直接實現便可,由於真實角色的主要任務就是唱歌。
/*** 真實明星類 * @author shengwu ni * @date 2018-12-08*/publicclassRealStarimplementsStar{@Overridepublicvoidsing(){System.out.println("明星本人開始唱歌……"); }}
代理類就須要作點工做了,咱們思考一下,代理只是在明星唱歌先後作一些準備和收尾的事,唱歌這件事還得明星親自上陣,代理作不了。因此代理類裏面是確定要將真實的對象傳進來。有了思路,咱們將代理類寫出來。
/*** 明星的靜態代理類 * * @author shengwu ni * @date 2018-12-08*/publicclassProxyStarimplementsStar{/** * 接收真實的明星對象 */private Star star;/** * 經過構造方法傳進來真實的明星對象 * @param star star*/publicProxyStar(Star star){this.star = star; }@Overridepublicvoidsing(){System.out.println("代理先進行談判……");// 唱歌只能明星本身唱this.star.sing(); System.out.println("演出完代理去收錢……"); }}
這樣的話,邏輯就很是清晰了。在代理類中,能夠看到,維護了一個Star對象,經過構造方法傳進來一個真實的Star對象給其賦值,而後在唱歌這個方法裏,使用真實對象來唱歌。因此說面談、收錢都是由代理對象來實現的,唱歌是代理對象讓真實對象來作。下面寫個客戶端測試下。
/*** 測試客戶端 * @author shengwu ni * @date 2018-12-08*/publicclassClient{/** * 測試靜態代理結果 * @param args args*/publicstaticvoidmain(String[] args){ Star realStar = new RealStar(); Star proxy = new ProxyStar(realStar); proxy.sing(); }}
讀者能夠本身運行下結果,靜態代理比較簡單。動態代理比靜態代理使用的更普遍,動態代理在本質上,代理類不用咱們來管,咱們徹底交給工具去生成代理類便可。動態代理通常有兩種方式:JDK 動態代理和 CGLIB 動態代理。
JDK 動態代理
既然動態代理不須要咱們去建立代理類,那咱們只須要編寫一個動態處理器就能夠了。真正的代理對象由 JDK 在運行時爲咱們動態的來建立。
/*** 動態代理處理類 * * @author shengwu ni * @date 2018-12-08*/publicclassJdkProxyHandler{/** * 用來接收真實明星對象 */private Object realStar;/** * 經過構造方法傳進來真實的明星對象 * * @param star star*/public JdkProxyHandler(Star star) {super();this.realStar = star; }/** * 給真實對象生成一個代理對象實例 * * @return Object */public Object getProxyInstance() {return Proxy.newProxyInstance(realStar.getClass().getClassLoader(),realStar.getClass().getInterfaces(), (proxy, method, args) -> {System.out.println("代理先進行談判……");// 唱歌須要明星本身來唱 Object object = method.invoke(realStar, args); System.out.println("演出完代理去收錢……");returnobject; }); }}
這裏說一下 Proxy.newProxyInstance() 方法,該方法接收三個參數:第一個參數指定當前目標對象使用的類加載器,獲取加載器的方法是固定的;第二個參數指定目標對象實現的接口的類型;第三個參數指定動態處理器,執行目標對象的方法時,會觸發事件處理器的方法。網上針對第三個參數的寫法都是 new 一個匿名類來處理,我這直接用的 Java8 裏面的 lamda 表達式來寫的,都同樣。底層原理使用的是反射機制。接下來寫一個客戶端程序來測試下。
/*** 測試客戶端 * @author shengwu ni * @date 2018-12-08*/publicclassClient{/** * 測試JDK動態代理結果 * @param args args*/publicstaticvoidmain(String[] args){ Star realStar = new RealStar();// 建立一個代理對象實例 Star proxy = (Star) new JdkProxyHandler(realStar).getProxyInstance(); proxy.sing(); }}
能夠看出,建立一個真實的對象,送給 JdkProxyHandler 就能夠建立一個代理對象了。
咱們對 JDK 動態代理作一個簡單的總結:相對於靜態代理,JDK 動態代理大大減小了咱們的開發任務,同時減小了對業務接口的依賴,下降了耦合度。JDK 動態代理是利用反射機制生成一個實現代理接口的匿名類,在調用具體方法前調用InvokeHandler 來處理。可是 JDK 動態代理有個缺憾,或者說特色:JDK 實現動態代理須要實現類經過接口定義業務方法。也就是說它始終沒法擺脫僅支持 interface 代理的桎梏,由於它的設計就註定了這個遺憾。
CGLIB 動態代理
由上面的分析可知,JDK 實現動態代理須要實現類經過接口定義業務方法,那對於沒有接口的類,如何實現動態代理呢,這就須要 CGLIB 了。
CGLIB 採用了很是底層的字節碼技術,其原理是經過字節碼技術爲一個類建立子類,並在子類中採用方法攔截的技術攔截全部父類方法的調用,順勢織入橫切邏輯。但由於採用的是繼承,因此不能對final修飾的類進行代理。咱們來寫一個 CBLIB 代理類。
/*** cglib代理處理類 * @author shengwu ni * @date 2018-12-08*/publicclassCglibProxyHandlerimplementsMethodInterceptor{/** * 維護目標對象 */private Object target;public Object getProxyInstance(final Object target) {this.target = target;// Enhancer類是CGLIB中的一個字節碼加強器,它能夠方便的對你想要處理的類進行擴展 Enhancer enhancer = new Enhancer();// 將被代理的對象設置成父類enhancer.setSuperclass(this.target.getClass());// 回調方法,設置攔截器enhancer.setCallback(this);// 動態建立一個代理類return enhancer.create();}@Overridepublic Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {System.out.println("代理先進行談判……");// 唱歌須要明星本身來唱 Object result = methodProxy.invokeSuper(object, args); System.out.println("演出完代理去收錢……");return result; }}
使用 CGLIB 須要實現 MethodInterceptor 接口,並重寫intercept 方法,在該方法中對原始要執行的方法先後作加強處理。該類的代理對象可使用代碼中的字節碼加強器來獲取。接下來寫個客戶端測試程序。
/*** 測試客戶端 * @author shengwu ni * @date 2018-12-08*/publicclassClient{/** * 測試Cglib動態代理結果 * @param args args*/publicstaticvoidmain(String[] args){ Star realStar = new RealStar(); Star proxy = (Star) new CglibProxyHandler().getProxyInstance(realStar);proxy.sing(); }}
這個客戶端測試程序和 JDK 動態代理的邏輯如出一轍,因此也能夠看出,代理模式中的動態代理,其實套路都是相同的,只是使用了不一樣的技術而已。
咱們也對 CGLIB 動態代理作一下總結:CGLIB 建立的動態代理對象比 JDK 建立的動態代理對象的性能更高,可是 CGLIB 建立代理對象時所花費的時間卻比 JDK 多得多。因此對於單例的對象,由於無需頻繁建立對象,用 CGLIB 合適,反之使用JDK方式要更爲合適一些。同時因爲 CGLIB 因爲是採用動態建立子類的方法,對於final修飾的方法沒法進行代理。
固然了,不論是哪一種動態代理技術,在上面的代碼裏,要代理的類中可能不止一種方法,有時候咱們須要對特定的方法進行加強處理,因此能夠對傳入的 method 參數進行方法名的判斷,再作相應的處理。
Spring AOP 採用哪一種代理?
JDK 動態代理和 CGLIB 動態代理均是實現 Spring AOP 的基礎。對於這一塊內容,面試官問的比較多,他們每每更想聽聽面試者是怎麼回答的,有沒有看過這一塊的源碼等等。
針對於這一塊內容,咱們看一下 Spring 5 中對應的源碼是怎麼說的。
publicclassDefaultAopProxyFactoryimplementsAopProxyFactory, Serializable{@Overridepublic AopProxy createAopProxy(AdvisedSupport config)throws AopConfigException {if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {Class<?> targetClass = config.getTargetClass();if (targetClass == null) {thrownew AopConfigException("TargetSource cannot determine target class: " +"Either an interface or a target is required for proxy creation."); }// 判斷目標類是不是接口或者目標類是否Proxy類型,如果則使用JDK動態代理if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {returnnew JdkDynamicAopProxy(config); }// 配置了使用CGLIB進行動態代理或者目標類沒有接口,那麼使用CGLIB的方式建立代理對象returnnew ObjenesisCglibAopProxy(config); }else {// 上面的三個方法沒有一個爲true,那使用JDK的提供的代理方式生成代理對象returnnew JdkDynamicAopProxy(config); } }//其餘方法略……}
從上述源碼片斷能夠看出,是否使用 CGLIB 是在代碼中進行判斷的,判斷條件是 config.isOptimize()、config.isProxyTargetClass() 和 hasNoUserSuppliedProxyInterfaces(config)。
其中,config.isOptimize() 與 config.isProxyTargetClass()默認返回都是 false,這種狀況下判斷結果就由hasNoUserSuppliedProxyInterfaces(config)的結果決定了。
簡單來講,hasNoUserSuppliedProxyInterfaces(config) 就是在判斷代理的對象是否有實現接口,有實現接口的話直接走 JDK 分支,即便用 JDK 的動態代理。
因此基本上能夠總結出 Spring AOP 中的代理使用邏輯了:若是目標對象實現了接口,默認狀況下會採用 JDK 的動態代理實現 AOP;若是目標對象沒有實現了接口,則採用 CGLIB 庫,Spring 會自動在 JDK 動態代理和 CGLIB 動態代理之間轉換。
固然,源碼我也沒讀那麼深,暫且就只能寫到這,後面深刻了,有新的看法再給你們分享。還記得文章開頭的幾個問題嗎?相信你讀到這裏,心中應該已經有了答案了。