代理模式-JDK動態代理

代理模式-JDK動態代理

關注公衆號 JavaStorm 獲取更多技術。java

爲另外一個對象提供表明,以便控制客戶對對象的訪問。其定義爲:爲另外一個對象提供替身或佔位符以訪問這個對象。具體地嗎能夠瀏覽 github.com/UniqueDong/…git

什麼是代理

官話上講是一種設計模式,目的是但願代碼重用。跟咱們以往訪問對象的方式不一樣,代理模式不是直接經過目標對象,而是經過代理訪問咱們的目標對象以及方法。由於有的時候咱們沒法直接與目標對象創建聯繫或者,咱們要控制客戶端訪問。因此便經過代理來訪問咱們的真實對象。github

就比如「客戶」-> 「明星經紀人」-> 「明星」。咱們不是直接與明星聯繫,明星很忙的,要唱歌跳舞燙頭拍電影,給的價格足夠好,經紀人才告知明星接下這個任務。設計模式

主要角色

代理模式

  • Subject:被代理類與代理類都要實現的主題接口。
  • ConcreteSubject:被代理類,也叫委託類,也就是真實對象,真正幹活的。
  • Proxy:代理類,扮演者中介的角色,控制客戶端對真實對象的訪問、生成代理對象讓客戶端透明調用,經過代理咱們能夠屏蔽或者加工針對真實對象。

使用場景

  • 一般更多出如今咱們經常使用的開源框架裏面,好比 Mybatis 中咱們 經過Mapper 接口就能調用到真正執行 SQL 的邏輯,其本質就是利用了動態代理,定位到真實的Statement執行。
  • Spring AOP 也是利用了動態代理,好比 Spring 事務,當調用的方法是被 Spring 事務管理的時候,其實他會生成一個代理類,封裝咱們的事務處理邏輯從而實現了事務加強,代碼複用。解放咱們開啓事務、執行邏輯 、提交事務或者回滾事務的模板代碼,咱們只要安心的編寫路基代碼便可。在這裏一共用到了 「模版方法模式」、動態代理模式。關於「模版方法」模式能夠參考歷史文章。
  • 還有 Dubbo RPC 框架,也是有使用動態代理。對於消費者咱們只是經過接口就能調用提供者所實現的功能,就像本地調用同樣。它爲咱們封裝了網絡傳輸、序列化、解碼編碼的繁瑣細節。就是生成了一個代理類爲屏蔽了底層細節。使得咱們能夠透明調用。

咱們根據加載被代理類的時機不一樣,將代理分爲靜態代理和動態代理。若是咱們在代碼編譯時就肯定了被代理的類是哪個,那麼就能夠直接使用靜態代理;若是不能肯定,那麼可使用類的動態加載機制,在代碼運行期間加載被代理的類這就是動態代理,好比RPC框架和Spring AOP機制。bash

靜態代理代碼示例

咱們按照 UML 類圖來實現一個簡單的靜態代理模式,首先先建立一個 Subject 接口。網絡

public interface Subject {

    public void request();
}

複製代碼

建立一個真實對象去實現該接口app

public class RealSubjbect implements Subject {

    @Override
    public void request() {
        System.out.println("this is RealSubjbect.request()");
    }
}

複製代碼

建立咱們的代理類,持有真實對象的引用,同時實現了 Subject 接口。框架

public class ProxySubject implements Subject {

    private Subject realSubjbect = null;

    /** * 除了代理真實角色作該作的事,代理角色提供附加操做 * 如 */
    @Override
    public void request() {
        preRequest(); //真實角色操做前的附加操做

        if (realSubjbect == null) {
            realSubjbect = new RealSubjbect();
        }
        realSubjbect.request();

        postRequest();//真實角色操做後的附加操做
    }
    /** * 真實角色操做前的附加操做 */
    private void postRequest() {
        System.out.println("真實角色操做後的附加操做");

    }

    /** * 真實角色操做後的附加操做 */
    private void preRequest() {
        System.out.println("真實角色操做前的附加操做");

    }
}

複製代碼

編寫咱們的客戶端接口,經過代理類調用。ide

public class Client {

    public static void main(String[] args) {
        Subject subject = new ProxySubject();
        subject.request(); //代理者代替真實者作事情
    }
}
複製代碼

打印的結果以下所示,咱們實現了對真實對象的控制,而且新增一些操做。就像Spring的AOP實現的功能同樣。post

真實角色操做前的附加操做
this is RealSubjbect.request()
真實角色操做後的附加操做
複製代碼

缺點

  1. 代理對象的一個接口只服務於一種類型的對象,若是要代理的方法不少,勢必要爲每一種方法都進行代理,靜態代理在程序規模稍大時就沒法勝任了。
  2. 若是接口增長一個方法,除了全部實現類須要實現這個方法外,全部代理類也須要實現此方法。增長了代碼維護的複雜度。

另外,若是要按照上述的方法使用代理模式,那麼真實角色(委託類)必須是事先已經存在的,並將其做爲代理對象的內部屬性。可是實際使用時,一個真實角色必須對應一個代理角色,若是大量使用會致使類的急劇膨脹;此外,若是事先並不知道真實角色(委託類),該如何使用代理呢?這個問題能夠經過Java的動態代理類來解決。

動態代理

概念

動態代理類的源碼是在程序運行期間 JVM 根據反射機制動態生成的,因此不存在代理類的字節碼文件。代理類和委託類的關係是在程序運行時肯定。

實現方式

主要有兩種實現方式:

  1. 使用 JDK 實現。委託類逆序有接口。
  2. 使用 CGLIB 實現。不能是final修飾的類,只能修飾 public 方法。

JDK 動態代理

UML 類圖以下所示

JDK 動態代理

  • 建立一個本身Handler 實現 InvocationHandler,同時持有真實對象的引用,會依賴咱們的業務類。
  • 代理對象經過 Proxy.newProxyInstance()生成,而該方法以來於 Handler對象和 真實對象 。

經過類圖其實咱們也能夠知道,當經過動態代理生成的代理字節碼調用的時候就會委託到 Handler 的 invoke 方法。同時 Handler 又持有真正的業務對象。因此能在執行調用真實的對象以前控制其行爲以及訪問。主要優勢:

  • 隱藏委託類的實現,調用者只須要和代理類進行交互。
  • 解耦合,在不改變委託類代碼狀況下作額外處理。好比添加 Dubbo 客戶端調用時的序列化、網絡傳輸細節。

代碼實現

經過類圖,建立 JDK 動態代理咱們能夠分爲三步。

  1. 定義被代理的業務接口以及實現類。
  2. 新建自定義的 InvocationHandler 實現 InvocationHandler接口,並依賴被代理類(真實對象的引用)。
  3. 經過 Proxy.newProxyInstance() 方法建立具體代理對象。

如今咱們有一個需求,爲咱們的業務邏輯統一記錄調用日誌,或者事務控制,在這裏咱們那就編寫一個日誌記錄爲咱們的被代理類記錄日誌。模擬相似 Spring AOP功能,一個簡化版的例子讓你們明白其使用場景以及原理。

第一步:咱們定義本身的業務接口 OrderServiceProductService

public interface OrderService {
    String createOder(String no);
}

public interface ProductService {
    String getProduct(String no);
}
複製代碼

業務邏輯的實現類

public class OrderServiceImpl implements OrderService {
    @Override
    public String createOder(String no) {
        return "生成訂單" + no + "成功";
    }
}

public class ProductServiceImpl implements ProductService {
    @Override
    public String getProduct(String no) {
        return "獲取商品" + no + "成功";
    }
}
複製代碼

第二步:定義咱們的 Handler 實現 JDK 的 Invocationhandler,將持有的委託引用定義 Object 類型,這樣能夠代理全部須要日誌記錄的委託類,同時咱們有一個 bind()方法,用於設置 target 同時返回 對應的代理類讓客戶端調用。第三步的 Proxy.newProxyInstance() 咱們放在 bind 方法裏。代碼更加簡潔。

public class LoggerInterceptor implements InvocationHandler {

    /** * 持有委託類的引用 */
    private Object target;

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        long start = System.currentTimeMillis();
        System.out.println(proxy.getClass().getInterfaces()[0]);
        //經過反射調用
        System.out.println("Entered " + target.getClass().getName() + "-" + method.getName() + ",with arguments{" + args[0] + "}");
        //調用目標對象的方法
        Object result = method.invoke(target, args);
        long end = System.currentTimeMillis();
        System.out.println("執行結果:" + result.toString());
        System.out.println("共耗時:" + (end - start));
        return result;
    }

    /** * 獲取代理類:並將 target 委託類綁定到 咱們定義的Targe中 * @param target 真實的委託類 * @return */
    public synchronized Object bind(Object target) {
        this.target = target;
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }
}
複製代碼

最後咱們新建啓動類看效果,分別有兩個業務邏輯類須要日誌記錄。具體可看代碼註釋

public class BootStrap {
    public static void main(String[] args) {
      // 新建業務邏輯
        OrderService orderService = new OrderServiceImpl();
        ProductService productService = new ProductServiceImpl();
			// 新建咱們的 日誌 Handler
        LoggerInterceptor loggerInterceptor = new LoggerInterceptor();

      //綁定 委託類同時生產代理類調用 。
        OrderService orderServiceProxy = (OrderService) loggerInterceptor.bind(orderService);
        orderServiceProxy.createOder("12927381");

        ProductService productServiceProxy = (ProductService) loggerInterceptor.bind(productService);
        productServiceProxy.getProduct("34010234");
    }
}

複製代碼

結果打印以下所示:咱們能夠看到,每一個咱們所代理的接口執行方法先後都打印了日誌以及返回結果。其實 Spring AOP 就是經過動態代理實現。

nterface com.zero.headfirst.proxy.service.OrderService
Entered com.zero.headfirst.proxy.service.OrderServiceImpl-createOder,with arguments{12927381}
執行結果:生成訂單12927381成功
共耗時:2
interface com.zero.headfirst.proxy.service.ProductService
Entered com.zero.headfirst.proxy.service.ProductServiceImpl-getProduct,with arguments{34010234}
執行結果:獲取商品34010234成功
共耗時:1

複製代碼

CGLIB 動態代理

CGLIB是一個強大的高性能的代碼生成包。它普遍的被許多AOP的框架使用,例如Spring AOP爲他們提供方法的interception(攔截)。CGLIB包的底層是經過使用一個小而快的字節碼處理框架ASM,來轉換字節碼並生成新的類。

不能是 final 修飾的類,以及final方法,能夠不定義接口。

使用方式很簡單:咱們要先添加cglib 依賴 ,新建一個類實現 MethodInterceptor 。

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class LoggerProxy implements MethodInterceptor {


    /** * 建立代理類 * @param targetClass 委託類 * @return */
    public Object bind(Class targetClass) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(targetClass);
        //設置回調方,當客戶端經過代理調用方法的時候會會調用咱們重寫的 intercept() 方法
        enhancer.setCallback(this);
        //建立代理類
        return enhancer.create();
    }


    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        long start = System.currentTimeMillis();
        //經過反射調用
        System.out.println("Entered " + o.getClass().getName() + "-" + method.getName() + ",with arguments{" + args[0] + "}");
        Object result = methodProxy.invokeSuper(o, args);
        long end = System.currentTimeMillis();
        System.out.println("執行結果:" + result.toString());
        System.out.println("共耗時:" + (end - start));
        return result;
    }
}

複製代碼

是否是很簡單?新建 Enhancer ,只要設置好 委託類以及回調類。在這裏咱們能夠利用工廠模式 建立不一樣的代理類對應的回調。這裏簡單實例就不寫了。

接下來看咱們的測試類以及結果

public class CglibBootStrap {
    public static void main(String[] args) {

        LoggerProxy loggerProxy = new LoggerProxy();

        OrderService orderService = (OrderService) loggerProxy.bind(OrderServiceImpl.class);
        orderService.createOder("12873051209g");


        ProductService productProxy = (ProductService) loggerProxy.bind(ProductServiceImpl.class);
        productProxy.getProduct("2780935782309");


    }
}

複製代碼

打印結果以下所示

Entered com.zero.headfirst.proxy.service.OrderServiceImpl$$EnhancerByCGLIB$$54d983a1-createOder,with arguments{12873051209g}
執行結果:生成訂單12873051209g成功
共耗時:14
Entered com.zero.headfirst.proxy.service.ProductServiceImpl$$EnhancerByCGLIB$$4e2c7c36-getProduct,with arguments{2780935782309}
執行結果:獲取商品2780935782309成功
共耗時:5

複製代碼

相關文章
相關標籤/搜索