代理模式是開發中經常使用的一種設計模式,每一種設計模式的出現都會極大的解決某方面的問題,代理模式也是同樣,本文將會用通俗的語言來解釋什麼是代理模式?代理模式的種類、代碼示例、每種代理模式的優缺點和代理模式適用的場景。html
首先咱們用一個小故事來描述下什麼是代理模式,這會讓你更快的理解代理模式的相關角色,爲後面的各類代理打下基礎。java
假如,你是一個大明星,人氣很旺,粉絲也特別多。由於人氣高,因此不少商家想找你代言廣告,可是想要找你代言的人特別多,每一個商家你都須要進行商務洽談,若是聊得不錯決定合做,後續還須要簽署不少合同文件、記錄、備案等。這麼多商家找你代言,其中你只能選擇其中幾個代言,即使只選擇幾個,你也忙不過來。因而你就想了一個辦法,給本身找了一個經紀人,給經紀人制定標準讓他去對接各商家,經紀人作事很認真負責,不只剔除了不少不良的商家還對有資格的商家作了詳細的記錄,記錄商家的代言費、商家詳細信息、商家合同等信息。因而在商務代言這件事情上你只須要專心代言拍廣告,其餘的事情交由經紀人一併處理。面試
分析下整個事件,能夠知道,經紀人就是代理人,明星就是被代理人。在明星的廣告代言中,經紀人處理的商務洽談和簽約環節至關於代理,這就是代理模式在實際生活中的簡單案例。spring
其實不止經紀人和明星,生活中還有不少行爲本質就是代理模式,好比:某些大牌的飲料三級代理銷售、酒水的省市縣的代理人、三國時曹操挾天子以令諸侯等等。數據庫
說了這麼多案例,都是關於代理模式的,那既然這麼多人都在用代理模式,那代理模式必定解決了生活中的某些棘手的問題,那到底是什麼問題呢?編程
在明星和經紀人這個案例中,由於把代言這個商業行爲作了細分,讓明星團隊中每一個人負責代言的一部分,使每人只須要專一於本身的事,提升每一個人的專業度的同時,也提升了效率,這就叫專業,專人專事。由於經紀人專一廣告代言的代理行爲,商業經驗豐富,因此經紀人也能夠用他的專業知識爲其餘明星作廣告代言的代理,這就叫能力複用。設計模式
那麼,如何使用代碼展現經紀人代理明星的廣告行爲呢?這其中有是如何運用代理模式的呢?數組
類比上面的明星和經紀人的例子:安全
假若有個明星類,咱們想在調用明星類的代言方法以前作一些其餘操做好比權限控制、記錄等,那麼就須要一箇中間層,先執行中間層,在執行明星類的代言方法。多線程
那講到這裏,想必又有人問,直接在明星類上加一個權限控制、記錄等方法不就好了麼,爲何非要用代理呢?
這就是本文最重要的一個核心知識,程序設計中的一個原則:類的單一性原則。這個原則很簡單,就是每一個類的功能儘量單一,在這個案例中讓明星類保持功能單一,就是對代理模式的通俗解釋。
那爲何要保持類的功能單一呢?
由於只有功能單一,這個類被改動的可能性纔會最小,其餘的操做交給其餘類去辦。在這個例子中,若是在明星類里加上權限控制功能,那麼明星類就再也不是單一的明星類了,是明星加經紀人二者功能的合併類。
若是咱們只想用權限控制功能,使用經紀人的功能給其餘明星篩選廣告商家,若是二者合併,就要建立這個合併類,可是咱們只使用權限功能,這就致使功能不單一,長期功能的累加會使得代碼極爲混亂,難以複用。
因此類的單一性原則和功能複用在代碼設計上很重要,這也是使用代理模式的核心。
而這整個過程所涉及到的角色能夠分爲四類:
在java語言的發展中,出現了不少種代理方式,這些代理方式能夠分類爲兩類:靜態代理和動態代理,下面咱們就結合代碼實例解釋下,各種代理的幾種實現方式,其中的優缺點和適用的場景。
主題接口
package com.shuai.proxy; public interface IDBQuery { String request(); }
真實主題
package com.shuai.proxy.staticproxy; import com.shuai.proxy.IDBQuery; public class DBQuery implements IDBQuery { public DBQuery() { try { Thread.sleep(1000);//假設數據庫鏈接等耗時操做 } catch (InterruptedException ex) { ex.printStackTrace(); } } @Override public String request() { return "request string"; } }
代理類
package com.shuai.proxy.staticproxy; import com.shuai.proxy.IDBQuery; public class DBQueryProxy implements IDBQuery { private DBQuery real = null; @Override public String request() { // TODO Auto-generated method stub System.out.println("在此以前,記錄下什麼東西吧....."); //在真正須要的時候才能建立真實對象,建立過程可能很慢 if (real == null) { real = new DBQuery(); }//在多線程環境下,這裏返回一個虛假類,相似於 Future 模式 String result = real.request(); System.out.println("在此以後,記錄下什麼東西吧....."); return result; } }
Main客戶端
package com.shuai.proxy.staticproxy; import com.shuai.proxy.IDBQuery; public class Test { public static void main(String[] args) { IDBQuery q = new DBQueryProxy(); //使用代裏 q.request(); //在真正使用時才建立真實對象 } }
能夠看到,主題接口是IDBQuery,真實主題是DBQuery 實現了IDBQuery接口,代理類是DBQueryProxy,在代理類的方法裏實現了DBQuery類,而且在代碼裏寫死了代理先後的操做,這就是靜態代理的簡單實現,能夠看到靜態代理的實現優缺點十分明顯。
優勢:
使得真實主題處理的業務更加純粹,再也不去關注一些公共的事情,公共的業務由代理來完成,實現業務的分工,公共業務發生擴展時變得更加集中和方便。
缺點:
這種實現方式很直觀也很簡單,但其缺點是代理類必須提早寫好,若是主題接口發生了變化,代理類的代碼也要隨着變化,有着高昂的維護成本。
針對靜態代理的缺點,是否有一種方式彌補?可以不須要爲每個接口寫上一個代理方法,那就動態代理。
動態代理,在java代碼裏動態代理類使用字節碼動態生成加載
技術,在運行時生成加載類。
生成動態代理類的方法不少,好比:JDK 自帶的動態處理、CGLIB、Javassist、ASM 庫。
這裏介紹兩種很是經常使用的動態代理技術,面試時也會經常用到的技術:JDK 自帶的動態處理
、CGLIB
兩種。
Java提供了一個Proxy類,使用Proxy類的newInstance方法能夠生成某個對象的代理對象,該方法須要三個參數:
初次看見會有些不理解,不要緊,下面用一個實例來詳細展現JDK動態代理的實現:
代理類的實現
package com.shuai.proxy.jdkproxy; import com.shuai.proxy.staticproxy.DBQuery; import com.shuai.proxy.IDBQuery; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class DBQueryHandler implements InvocationHandler { private IDBQuery realQuery = null;//定義主題接口 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //若是第一次調用,生成真實主題 if (realQuery == null) { realQuery = new DBQuery(); } if ("request".equalsIgnoreCase(method.getName())) { System.out.println("調用前作點啥,助助興....."); Object result = method.invoke(realQuery, args); System.out.println("調用後作點啥,助助興....."); return result; } else { // 若是不是調用request方法,返回真實主題完成實際的操做 return method.invoke(realQuery, args); } } static IDBQuery createProxy() { IDBQuery proxy = (IDBQuery) Proxy.newProxyInstance( ClassLoader.getSystemClassLoader(), //當前類的類加載器 new Class[]{IDBQuery.class}, //被代理的主題接口 new DBQueryHandler() // 代理對象,這裏是當前的對象 ); return proxy; } }
Main客戶端
package com.shuai.proxy.jdkproxy; import com.shuai.proxy.IDBQuery; public class Test { // 客戶端測試方法 public static void main(String[] args) { IDBQuery idbQuery = DBQueryHandler.createProxy(); idbQuery.request(); } }
用debug的方式啓動,能夠看到方法被代理到代理類中實現,在代理類中執行真實主題的方法先後能夠進行不少操做。
雖然這種方法實現看起來很方便,可是細心的同窗應該也已經觀察到了,JDK動態代理技術的實現是必需要一個接口才行的,因此JDK動態代理的優缺點也很是明顯:
優勢:
缺點:
因爲必需要有接口才能使用JDK的動態代理,那是否有一種方式能夠沒有接口只有真實主題實現類也可使用動態代理呢?這就是第二種動態代理:CGLIB
;
使用 CGLIB
生成動態代理,首先須要生成 Enhancer
類實例,並指定用於處理代理業務的回調類。在 Enhancer.create()
方法中,會使用 DefaultGeneratorStrategy.Generate()
方法生成動態代理類的字節碼,並保存在 byte 數組中。接着使用 ReflectUtils.defineClass()
方法,經過反射,調用 ClassLoader.defineClass()
方法,將字節碼裝載到 ClassLoader 中,完成類的加載。最後使用 ReflectUtils.newInstance()
方法,經過反射,生成動態類的實例,並返回該實例。基本流程是根據指定的回調類生成 Class 字節碼—經過 defineClass()
將字節碼定義爲類—使用反射機制生成該類的實例。
真實主題
package com.shuai.proxy.cglibproxy; class BookImpl { void addBook() { System.out.println("增長圖書的普通方法..."); } }
代理類
package com.shuai.proxy.cglibproxy; import org.springframework.cglib.proxy.Enhancer; import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.cglib.proxy.MethodProxy; import java.lang.reflect.Method; public class BookImplProxyLib implements MethodInterceptor { /** * 建立代理對象 * * @return */ Object getBookProxyImplInstance() { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(BookImpl.class); // 回調方法 enhancer.setCallback(this); // 建立代理對象 return enhancer.create(); } // 回調方法 @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("開始..."); proxy.invokeSuper(obj, args); System.out.println("結束..."); return null; } }
Main客戶端
package com.shuai.proxy.cglibproxy; public class Test { public static void main(String[] args) { BookImplProxyLib cglib = new BookImplProxyLib(); BookImpl bookCglib = (BookImpl) cglib.getBookProxyImplInstance(); bookCglib.addBook(); } }
優勢:
CGLIB經過繼承的方式進行代理、不管目標對象沒有沒實現接口均可以代理,彌補了JDK動態代理的缺陷。
缺點:
代理模式有多種應用場合,以下所述: