基本介紹:
- 代理模式(Proxy)為一個物件提供一個替身,以控制對這個物件的訪問。即通過代理物件訪問目標物件.這樣做的好處是:可以在目標物件實現的基礎上,增強額外的功能操作,即擴充套件目標物件的功能,想在訪問一個類時做一些控制
- 代理的物件可以是遠端物件、建立開銷大的物件或需要安全控制的物件,代理模式有不同的形式, 主要有三種靜態代理(需要實際的類檔案)、動態代理 (JDK 代理、介面代理)和Cglib理代理(可以在記憶體動態的建立物件,而不需要實現介面, 他是屬於動態代理的範疇)
靜態代理:
-
靜態代理在使用時,需要定義介面或者父類,被代理物件(即目標物件)與代理物件一起實現相同的介面或者是繼承相同父類
-
代理物件類也需要建立一個檔案
-
UMl類圖
-
程式碼實現
-
public interface ITeacherDao { // 介面父類 void teach(); } // 真實物件實現類 class TeacherDao implements ITeacherDao { @Override public void teach() { System.out.println("目標物件teach..."); } } // 代理物件實現類 class TeacherDaoProxy implements ITeacherDao { // 聚合真實物件(本次使用的介面) private ITeacherDao teacherDao; public TeacherDaoProxy(ITeacherDao teacherDao) { this.teacherDao = teacherDao; } @Override public void teach() { // 在執行真實目標方法前後可做一些額外的事情,對真實目標方法進行強 System.out.println("靜態代理前置增強"); // 執行真實物件的目標方法 teacherDao.teach(); System.out.println("靜態代理後置增強"); } }
-
public class Client { public static void main(String[] args) { // 建立代理物件 TeacherDaoProxy teacherDaoProxy = new TeacherDaoProxy(new TeacherDao()); // 執行增強的目標方法 teacherDaoProxy.teach(); } }
-
-
優點:在不修改目標物件的功能前提下, 能通過代理物件對目標功能擴充套件
-
缺點:因為代理物件需要與目標物件實現一樣的介面,所以會有很多代理類,一旦介面增加方法,目標物件與代理物件都要維護
jdk動態代理:
-
代理物件,不需要實現介面,但是目標物件要實現介面,否則不能用動態代理
-
代理物件的生成,是利用JDK的API,動態的在記憶體中構建代理物件
-
動態代理也叫做:JDK代理、介面代理
-
jdk動態代理生成的代理物件和真實物件時兄弟關係,都有共同的父介面,必須得有介面
-
UMl類圖
-
程式碼實現
-
public class ProxyFactory { private Object object; public ProxyFactory(Object object) { this.object = object; } public Object getProxyInstance() { /**直接使用jdk的Proxy類的newProxyInstance即可 注意:生成的代理物件和真實物件是兄弟關係 * 引數1:目標物件的類載入器 * 引數2:目標物件的介面陣列(為了方便知道目標物件中有哪些方法,代理物件可以呼叫) * 引數3:事件處理,代理物件呼叫方法時,所呼叫的邏輯(直接使用匿名內部類) */ return Proxy.newProxyInstance( this.object.getClass().getClassLoader(), this.object.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { /** * 代理物件呼叫方法時,所呼叫的邏輯 * 引數1:代理物件 引數2:所呼叫的真實方法 引數3:呼叫真實方法的引數列表 * 在執行真實目標方法前後可做一些額外的事情,對真實目標方法進行強 * 通過反射呼叫真實物件的真實方法 */ System.out.println("jdk動態代理前置增強"); // 執行真實物件的目標方法 Object invoke = method.invoke(object, args); System.out.println("jdk動態代理後置增強"); return invoke; } }); } }
-
public class Client { public static void main(String[] args) { // 建立代理工廠 ProxyFactory proxyFactory = new ProxyFactory(new TeacherDao()); // 獲取代理物件 呼叫增強方法 ITeacherDao teacherDao = (ITeacherDao) proxyFactory.getProxyInstance(); teacherDao.teach(); System.out.println(teacherDao.getClass()); // 在記憶體中動態生成的代理物件 class com.sun.proxy.$Proxy0 } }
-
cglib動態代理:
-
靜態代理和JDK代理模式都要求目標物件是實現一個介面,但是有時候目標物件只是一個單獨的物件,並沒有實現任何的介面,這個時候可使用目標物件子類來實現代理-這就是Cglib代理
-
Cglib代理也叫作子類代理,它是在記憶體中構建一個子類物件從而實現對目標物件功能擴充套件
-
Cglib是一個強大的高效能的程式碼生成包,它可以在執行期擴充套件java類與實現java介面.它廣泛的被許多AOP的框架使用,例如 SpringAOP,實現方法攔截
-
在AOP程式設計中如何選擇代理模式:目標物件需要實現介面,用JDK代理目標物件,不需要實現介面,用Cglib代理
-
Cglib包的底層是通過使用位元組碼處理框架ASM來轉換位元組碼並生成新的類
-
注意問題
- 在記憶體中動態構建子類,注意代理的類不能為 final,否則報錯 java.lang.IllegalArgumentException
- 目標物件的方法如果為 final/static,那麼就不會被攔截,即不會執行目標物件額外的業務方法
-
UML類圖
-
程式碼實現
-
<!--pom 中匯入cglib依賴--> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>2.1_3</version> </dependency>
-
public class ProxyFactory implements MethodInterceptor { private Object object; public ProxyFactory(Object object) { this.object = object; } public Object getProxyInstance() { // 建立工具類 或使用靜態方法 Enhancer.create() 其中所需要的回撥物件直接使用匿名內部類 new MethodInterceptor Enhancer enhancer = new Enhancer(); // 設定父類 注意所生成的代理物件和真實物件是父子關係,所以要求父類不能被final修飾 所攔截的方法不能是final和static enhancer.setSuperclass(this.object.getClass()); // 設定回撥 this代表本身,代理物件執行方法時會呼叫MethodInterceptor介面的intercept方法,而本類實現了該介面,所以直接設定this即可 enhancer.setCallback(this); // 建立代理物件 return enhancer.create(); } @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { /** * 這個方法和jdk動態代理中的new InvocationHandler 中的 invoke方法作用一樣 起攔截作用 * 引數1:代理物件 引數2:執行的真實物件的方法 引數3:真實方法的引數列表 引數4:真實方法的代理物件 * 在執行真實目標方法前後可做一些額外的事情,對真實目標方法進行強 * 通過反射呼叫真實物件的真實方法 */ System.out.println("cglib動態代理前置增強"); // 執行真實物件的目標方法 Object invoke = method.invoke(object, objects); System.out.println("cglib動態代理後置增強"); return invoke; } }
-
public class Client { public static void main(String[] args) { // 建立代理工廠 ProxyFactory proxyFactory = new ProxyFactory(new TeacherDao()); // 獲取代理物件 呼叫增強方法 為什麼debug時會呼叫多次? ITeacherDao teacherDao = (ITeacherDao) proxyFactory.getProxyInstance(); teacherDao.teach(); System.out.println(teacherDao.getClass()); // 在記憶體中動態生成的代理物件 TeacherDao$$EnhancerByCGLIB$$a5278286 } }
-
其他代理:
- 防火牆代理:內網通過代理穿透防火牆,實現對公網的訪問
- 快取代理:當請求圖片檔案等資源時,先到快取代理取,如果取到資源則ok,如果取不到資源,再到公網或者資料庫取,然後快取
- 遠端代理:遠端物件的本地代表,通過它可以把遠端物件當本地物件來呼叫,遠端代理通過網路和真正的遠端物件溝通訊息
- 同步代理:主要使用在多執行緒程式設計中,完成多執行緒間同步工作(通過代理物件和真實物件和進行互動來抱枕執行緒安全)
注意事項:
- 缺點:由於在客戶端和真實主題之間增加了代理物件,因此有些型別的代理模式可能會造成請求的處理速度變慢。 實現代理模式需要額外的工作,有些代理模式的實現非常複雜
- 和介面卡模式的區別:介面卡模式主要改變所考慮物件的介面,而代理模式不能改變所代理類的介面
- 和裝飾器模式的區別:裝飾器模式為了增強功能,而代理模式是為了加以控制