代理模式是一種理論上很是簡單,可是各類地方的實現每每卻很是複雜。本文將從代理模式的基本概念出發,探討代理模式在java領域的應用與實現。讀完本文你將get到如下幾點:java
在生活中咱們一般是去商場購買東西,而不是去工廠。最主要的緣由可能有如下幾種:程序員
在面向對象的系統中也有一樣的問題,有些對象因爲某種緣由,好比對象建立開銷很大,或者某些操做須要安全控制等,直接訪問會給使用者或者系統結構帶來不少麻煩,這時咱們就須要考慮使用代理模式。面試
在應用中咱們可能會用代理模式解決如下問題:spring
代理模式:爲其餘對象提供一種代理以控制(隔離,使用接口)對這個對象的訪問。類圖以下:編程
所謂控制,其實使用接口隔離其餘對象與這個對象之間的交互;就是爲client對象對RealSubject對象的訪問一種隔離,本質上就是CLient→RealSuject的關係變成了Client→Subject, Proxy→RealSubject。 須要注意的時,代理類(Proxy)並不必定要求保持接口的完整的一致性(既也能夠徹底不需實現Subject接口),只要可以實現間接控制便可。安全
背景:假設已有一個訂單系統,能夠保存訂單信息。cookie
需求:打印保存訂單信息消耗時間。ide
/** * 訂單服務 * * @author cruder * @date 2019-11-23 15:42 **/ public class OrderService2 { /** * 保存訂單接口 */ public void saveOrder(String orderInfo) throws InterruptedException { // 隨機休眠,模擬訂單保存須要的時間 Thread.sleep(System.currentTimeMillis() & 100); System.out.println("訂單:" + orderInfo + " 保存成功"); } }
直接修改源代碼,這一般也是最簡單和最容易想到的實現。函數
/** * 保存訂單接口, 直接修改代碼 */ public void saveOrder(String orderInfo) throws InterruptedException { long start = System.currentTimeMillis(); // 隨機休眠,模擬訂單保存須要的時間 Thread.sleep(System.currentTimeMillis() & 100); System.out.println("訂單:" + orderInfo + " 保存成功"); System.out.println("保存訂單用時: " + (System.currentTimeMillis() - start) + "ms"); }
面向對象設計原則中的「開閉原則」告訴咱們,開閉原則規定「軟件中的對象(類,模塊,函數等等)應該對於擴展是開放的,可是對於修改是封閉的」,這意味着一個實體是容許在不改變它的源代碼的前提下變動它的行爲。工具
/** * 1. 定義接口,爲了使代理被代理對象看起來同樣。固然這一步徹底能夠省略 * * @author cruder * @date 2019-11-23 15:58 **/ public interface IOrderService { /** * 保存訂單接口 * @param orderInfo 訂單信息 */ void saveOrder(String orderInfo) throws InterruptedException; } /** * 2. 原有訂單服務,也實現這個接口。注意 此步驟也徹底能夠省略。 * * @author cruder * @date 2019-11-23 15:42 **/ public class OrderService implements IOrderService{ /** * 保存訂單接口 */ @Override public void saveOrder(String orderInfo) throws InterruptedException { // 隨機休眠,模擬訂單保存須要的時間 Thread.sleep(System.currentTimeMillis() & 100); System.out.println("訂單:" + orderInfo + " 保存成功"); } } /** * 3. 建立代理類,實現訂單服務接口【這纔是代理模式的實現】 * * @author cruder * @date 2019-11-23 16:01 **/ public class OrderServiceProxy implements IOrderService{ /** * 內部持有真實的訂單服務對象,保存訂單工做實際由它來完成 */ private IOrderService orderService; @Override public void saveOrder(String orderInfo) throws InterruptedException { /** * 延遲初始化,也能夠建立代理對象時就建立,或者做爲構造參數傳進來 * 僅做爲代碼實例,不考慮線程安全問題 */ if (orderService == null) { orderService = new OrderService(); } long start = System.currentTimeMillis(); orderService.saveOrder(orderInfo); System.out.println("保存訂單用時: " + (System.currentTimeMillis() - start) + "ms"); } }執行程序
執行程序
優勢: 一、職責清晰。 二、高擴展性。 三、智能化。
缺點:
一、因爲在客戶端和真實主題之間增長了代理對象,所以有些類型的代理模式可能會形成請求的處理速度變慢。 二、實現代理模式須要額外的工做,有些代理模式的實現很是複雜。
在java中代理模式能夠按照代理類的建立時機分兩類,即靜態代理和動態代理,而動態代理又能夠分爲jdk動態代理和cglib動態代理。每種實現方式都各有千秋,接下來筆者將回針對不一樣的實現方式進行演示和剖析。
在上文代理模式代碼演進中就使用了靜態代理模式。所謂靜態代理中的「靜」字,無非就是代理類的建立時機不一樣罷了。靜態代理須要爲每一個被代理的對象手動建立一個代理類;而動態代理則時在運行時經過某種機制來動態生成,不須要手動建立代理類。
jdk動態代理模式是利用java中的反射技術,在運行時動態建立代理類。接下來咱們仍藉助上文中的訂單服務的案例,使用jdk動態代理實現。
基於動態jdk涉及到兩個核心的類Proxy類和一個 InvocationHandler接口。
/** * 基於JDK技術 動態代理類技術核心 Proxy類和一個 InvocationHandler 接口 * * @author cruder * @date 2019-11-23 16:40 **/ public class ProxyFactory implements InvocationHandler { /** * 委託對象,既被代理的對象 */ private Object target; public ProxyFactory (Object target) { this.target = target; } /** * 生成代理對象 * 1. Classloader loader: 制定當前被代理對象使用的累加子啊其,獲取加載器的方法固定 * 2. Class<?>[] interfaces: 委託類的接口類型,使用泛型方法確認類型 * 3. InvocationHandler handler: 事件處理,執行委託對象的方法時會觸發事件處理器方法, * 會把當前執行的委託對象方法做爲參數傳入 */ public Object getProxyInstance() { Class clazz = target.getClass(); return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { long start = System.currentTimeMillis(); method.invoke(target, args); System.out.println("保存訂單用時: " + (System.currentTimeMillis() - start) + "ms"); return null; } } /** * 經過動態代理方式來保存訂單 * * @author cruder * @date 2019-11-23 15:49 **/ public class Client { public static void main(String[] args) throws InterruptedException { ProxyFactory proxyFactory= new ProxyFactory (new OrderService()); IOrderService orderService = (IOrderService) proxyFactory.getProxyInstance(); orderService.saveOrder(" cruder 新買的花褲衩 "); } }
以上即是jdk動態代理的所有實現,有種只可意會不可言傳的感受,筆者始終感受這種實現看起來很彆扭。不過也要強行總結如下,jdk實現動態代理能夠分爲如下幾個步驟:
代理類源碼閱讀
上文中基於jdk動態代理的代碼實現中對於可*的產品經理來講已經徹底知足了需求,可是對於具備Geek精神的程序員來講這遠遠不夠,對於這種不知其因此然的東西每每讓人感到不安。接下來咱們將經過自定義的一個小工具類將動態生成的代理類保存到本地來一看究竟。
/** * 將生成的代理類保存爲.class文件的工具類 * * @author cruder * @date 2019-08-15 0:27 */ public class ProxyUtils { /** * 將代理類保存到指定路徑 * * @param path 保存到的路徑 * @param proxyClassName 代理類的Class名稱 * @param interfaces 代理類接口 * @return */ public static boolean saveProxyClass(String path, String proxyClassName, Class[] interfaces){ if (proxyClassName == null || path == null) { return false; } // 獲取文件字節碼,而後輸出到目標文件中 byte[] classFile = ProxyGenerator.generateProxyClass(proxyClassName, interfaces); try (FileOutputStream out = new FileOutputStream(path)) { out.write(classFile); out.flush(); } catch (IOException e) { e.printStackTrace(); return false; } return true; } }
// 此處是重點, 生成的代理類實現了IOrderService,而且繼承了Proxy public final class $Proxy0 extends Proxy implements IOrderService { private static Method m1; private static Method m3; private static Method m2; private static Method m0; public $Proxy0(InvocationHandler var1) throws { super(var1); } public final boolean equals(Object var1) throws { try { return (Boolean)super.h.invoke(this, m1, new Object[]{var1}); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } } public final void saveOrder(Order var1) throws { try { super.h.invoke(this, m3, new Object[]{var1}); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } } public final String toString() throws { try { return (String)super.h.invoke(this, m2, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final int hashCode() throws { try { return (Integer)super.h.invoke(this, m0, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } static { try { // 經過反射獲取Method對象 m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); m3 = Class.forName("cn.mycookies.test08proxy.IOrderService").getMethod("saveOrder", Class.forName("cn.mycookies.test08proxy.Order")); m2 = Class.forName("java.lang.Object").getMethod("toString"); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } }
ps: 實習轉正面試中被問到爲何jdk動態代理被代理的類爲何要實現接口?
對於cglib我想大多數人應該都很陌生,或者是在學習Spring中AOP(面向切面編程)時據說了它使用jdk和cglib兩種方式實現了動態代理。接下來筆者將針對cglib進行簡要介紹。
cglib動態代理和jdk動態代理相似,也是採用操做字節碼機制,在運行時生成代理類。cglib 動態代理採起的是建立目標類的子類的方式,由於是子類化,咱們能夠達到近似使用被調用者自己的效果。
字節碼處理機制-指得是ASM來轉換字節碼並生成新的類
注:spring中有完整的cglib相關的依賴,因此如下代碼基於spring官方下載的demo中直接進行編寫的
/** * 1. 訂單服務-委託類,不須要再實現接口 * * @author cruder * @date 2019-11-23 15:42 **/ public class OrderService { /** * 保存訂單接口 */ public void saveOrder(String orderInfo) throws InterruptedException { // 隨機休眠,模擬訂單保存須要的時間 Thread.sleep(System.currentTimeMillis() & 100); System.out.println("訂單:" + orderInfo + " 保存成功"); } } /** * cglib動態代理工廠 * * @author cruder * @date 2019-11-23 18:36 **/ public class ProxyFactory implements MethodInterceptor { /** * 委託對象, 即被代理對象 */ private Object target; public ProxyFactory(Object target) { this.target = target; } /** * 返回一個代理對象 * @return */ public Object getProxyInstance(){ // 1. 建立一個工具類 Enhancer enhancer = new Enhancer(); // 2. 設置父類 enhancer.setSuperclass(target.getClass()); // 3. 設置回調函數 enhancer.setCallback(this); // 4.建立子類對象,即代理對象 return enhancer.create(); } @Override public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { long start = System.currentTimeMillis(); Object result = method.invoke(target, args); System.out.println("cglib代理:保存訂單用時: " + (System.currentTimeMillis() - start) + "ms"); return result; } }
/** * 使用cglib代理類來保存訂單 * * @author cruder * @date 2019-11-23 15:49 **/ public class Client { public static void main(String[] args) throws InterruptedException { // 1. 建立委託對象 OrderService orderService = new OrderService(); // 2. 獲取代理對象 OrderService orderServiceProxy = (OrderService) new ProxyFactory(orderService).getProxyInstance(); String saveFileName = "CglibOrderServiceDynamicProxy.class"; ProxyUtils.saveProxyClass(saveFileName, orderService.getClass().getSimpleName(), new Class[]{IOrderService.class}); orderServiceProxy.saveOrder(" cruder 新買的花褲衩 "); } }
cglib動態代理實現步驟和jdk及其類似,能夠分爲如下幾個步驟:
cglib動態代理生成的代理類和jdk動態代理代碼格式上幾乎沒有什麼區別,惟一的區別在於cglib生成的代理類繼承了僅僅Proxy類,而jdk動態代理生成的代理類繼承了Proxy類的同時也實現了一個接口。代碼以下:
// 生成一個Proxy的子類 public final class OrderService extends Proxy { private static Method m1; private static Method m2; private static Method m0; public OrderService(InvocationHandler var1) throws { super(var1); } public final boolean equals(Object var1) throws { try { return (Boolean)super.h.invoke(this, m1, new Object[]{var1}); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } } public final String toString() throws { try { return (String)super.h.invoke(this, m2, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final int hashCode() throws { try { return (Integer)super.h.invoke(this, m0, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); m2 = Class.forName("java.lang.Object").getMethod("toString"); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } }
JDK Proxy 的優點:
cglib 優點: