12.java設計模式之代理模式
基本介紹:
- 代理模式(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,如果取不到資源,再到公網或者數據庫取,然後緩存
- 遠程代理:遠程對象的本地代表,通過它可以把遠程對象當本地對象來調用,遠程代理通過網絡和真正的遠程對象溝通信息
- 同步代理:主要使用在多線程編程中,完成多線程間同步工作(通過代理對象和真實對象和進行交互來抱枕線程安全)
注意事項:
- 缺點:由於在客戶端和真實主題之間增加了代理對象,因此有些類型的代理模式可能會造成請求的處理速度變慢。 實現代理模式需要額外的工作,有些代理模式的實現非常複雜
- 和適配器模式的區別:適配器模式主要改變所考慮對象的接口,而代理模式不能改變所代理類的接口
- 和裝飾器模式的區別:裝飾器模式為了增強功能,而代理模式是為了加以控制