代理模式(動態代理)

靜態代理模式的缺點

當場景稍微複雜一些的時候,靜態代理的缺點也會暴露出來:
1、當需要代理多個類的時候,由於代理對象要實現與目標對象一致的接口,如果只維護一個代理類,由這個代理類實現多個接口,但是這樣就導致代理類過於龐大;如果新建多個代理類,每個目標對象對應一個代理類,但是這樣會產生過多的代理類。
2、 當接口需要增加、刪除、修改方法的時候,目標對象與代理類都要同時修改,不易維護。


動態代理

類的動態生成

這關係到Java虛擬機的類加載過程,JVM會通過一個類的全限定名來獲取定義此類的二進制字節流,並將這個字節流所代表的靜態存儲結構轉化爲方法區的運行時數據結構,也就是Java中的一個類在JVM中就是一個數據結構。

動態代理就是想辦法操作類的字節碼文件,在程序運行期間可以修改或新建類。

常見的字節碼操作類庫
  1. ObjectWeb ASM:一個Java字節碼操作框架。它能夠以二進制形式修改已有類或者動態生成類。ASM 可以直接產生二進制 class 文件,也可以在類被加載入 Java 虛擬機之前動態改變類行爲。ASM 從類文件中讀入信息後,能夠改變類行爲,分析類信息,甚至能夠根據用戶要求生成新類。
  2. JDK動態代理:代理對象是由JDK動態生成的,JDK動態代理基於攔截器和反射來實現。JDK代理是不需要第三方庫支持的,只需要JDK環境就可以進行實現。
  3. CGLIB(Code Generation Library):一個功能強大,高性能和高質量的代碼生成庫,用於擴展JAVA類並在運行時實現接口。
  4. Javassist:Java的加載時反射系統,它是一個用於在Java中編輯字節碼的類庫;它使Java程序能夠在運行時定義新類,並在JVM加載之前修改類文件。
  5. Instrumentation 是 Java SE 5 的新特性,它把 Java 的 instrument 功能從本地代碼中解放出來,使之可以用 Java 代碼的方式解決問題。使用 Instrumentation,開發者可以構建一個獨立於應用程序的代理程序(Agent),用來監測和協助運行在 JVM 上的程序,甚至能夠替換和修改某些類的定義。
  6. AspectJ:AspectJ 庫屬於靜態織入,原理是靜態代理。

Java中動態代理的實現一般分爲兩種:JDK動態代理以及CGLIB動態代理。
在這裏插入圖片描述

兩種最常見的方式:

  • 通過實現接口的方式 -> JDK動態代理
  • 通過繼承類的方式 -> CGLIB動態代理

JDK動態代理

條件:

  1. 必須實現InvocationHandler接口
  2. 使用Proxy.newProxyInstance產生代理對象
  3. 被代理的對象必須要實現接口

JDK動態代理主要涉及兩個類:java.lang.reflect.Proxyjava.lang.reflect.InvocationHandler

JDK實現代理需要使用InvocationHandler接口:

public interface InvocationHandler {
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}

這是調用處理器接口,它自定義了一個 invoke 方法,用於集中處理在動態代理類對象上的方法調用,通常在該方法中實現對委託類的代理訪問。

JDK實現代理需要使用Proxy類的newProxyInstance方法:

public static Object newProxyInstance(ClassLoader loader, Class [] interfaces, InvocationHandler handler)

構造實現指定接口的代理類的一個新實例,所有方法會調用給定處理器對象的 invoke 方法。

三個參數依次爲:
ClassLoader loader 指定當前目標對象使用類加載器,用null表示默認類加載器
Class [] interfaces 需要實現的接口數組
InvocationHandler handler 調用處理器,執行目標對象的方法時,會觸發調用處理器的方法,從而把當前執行目標對象的方法作爲參數傳入

在這裏插入圖片描述

UserService

/**
 * 代理接口
 */
public interface UserService {

    void select();

    void update();

}

UserServiceImpl

/**
 * 需要被代理的類
 */
public class UserServiceImpl implements UserService {

    @Override
    public void select() {
        System.out.println("查詢 select by id");
    }

    @Override
    public void update() {
        System.out.println("更新 update by id");
    }

}

LogHandler

/**
 * 編寫一個調用邏輯處理器 LogHandler 類,提供日誌增強功能,並實現 InvocationHandler 接口;
 *
 * 在 LogHandler 中維護一個目標對象 target,這個對象是被代理的對象(真實主題角色);
 * 在 invoke 方法中編寫方法調用的邏輯處理
 */
public class LogHandler implements InvocationHandler {

    /**
     * 被代理的對象
     */
    private Object target;

    public LogHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object result = method.invoke(target, args);
        after();
        return result;
    }

    private void before() {
        System.out.println(String.format("task start time: %s", System.currentTimeMillis()));
    }

    private void after() {
        System.out.println(String.format("task end time: %s", System.currentTimeMillis()));
    }

}

Client

public class Client {

    public static void main(String[] args) {
        // 1. 創建被代理的對象,UserService接口的實現類
        UserServiceImpl userService = new UserServiceImpl();
        // 2. 獲取對應的 ClassLoader
        ClassLoader classLoader = userService.getClass().getClassLoader();
        // 3. 獲取所有接口的Class,這裏的UserServiceImpl只實現了一個接口UserService
        Class[] interfaces = userService.getClass().getInterfaces();
        // 4. 創建一個將傳給代理類的調用請求處理器,處理所有的代理對象上的方法調用
        LogHandler logHandler = new LogHandler(userService);

        /*
		   5.根據上面提供的信息來創建代理對象 在這個過程中:
               a.JDK會通過根據傳入的參數信息動態地在內存中創建和.class文件等同的字節碼
               b.然後根據相應的字節碼轉換成對應的class,
               c.然後調用newInstance()創建代理實例
		 */
        UserService proxyInstance = (UserService)Proxy.newProxyInstance(classLoader, interfaces, logHandler);
        proxyInstance.select();
        proxyInstance.update();




        // ============================================== Java8 =========================================
        UserService proxyInstance1 = (UserService) Proxy.newProxyInstance(UserService.class.getClassLoader(), new Class[]{UserService.class},
                (proxy, method, args1) -> {
                    System.out.println(String.format("task start time: %s", System.currentTimeMillis()));
                    try {
                        return method.invoke(userService, args1);
                    } finally {
                        System.out.println(String.format("task end time: %s", System.currentTimeMillis()));
                    }
                }
        );
        proxyInstance1.select();
        proxyInstance1.update();
    }

}

--------------------輸出-------------------
task start time: 1583423120396
查詢 select by id
task end time: 1583423120400
task start time: 1583423120400
更新 update by id
task end time: 1583423120400

task start time: 1583423120468
查詢 select by id
task end time: 1583423120469
task start time: 1583423120469
更新 update by id
task end time: 1583423120470

CGLIB動態代理

如果目標對象只是一個單獨的對象,並沒有實現任何的接口,這個時候就可以使用構建目標對象子類的方式實現代理,這種方法就叫做Cglib代理,也叫作子類代理,它是在內存中構建一個子類對象從而實現對目標對象功能的擴展。

Cglib是一個強大的高性能的代碼生成包,它可以在運行期擴展java類與實現java接口。它廣泛的被許多AOP的框架使用,例如Spring AOP和synaop,爲他們提供方法的interception(攔截)。

Cglib包的底層是通過使用字節碼處理框架ASM來轉換字節碼並生成新的子類。


條件:

  1. 需要引入CGLIB第三方包;
  2. 被代理的類不能爲final;
  3. 被代理對象的方法如果爲final/static,那麼就不會執行被代理對象額外的業務方法;

Cglib代理需要MethodInterceptor接口,這個接口只有一個intercept()方法:

public interface MethodInterceptor extends Callback {
    Object intercept(Object var1, Method var2, Object[] var3, MethodProxy var4) throws Throwable;
}

這個方法有4個參數:
obj 表示增強的對象,即實現這個接口類的一個對象;
method 表示要被攔截的方法;
args 表示要被攔截方法的參數;
proxy 表示要觸發父類的方法對象;

UserService

/**
 * 目標對象,沒有實現任何接口
 */
public class UserService {

    public void save() {
        System.out.println("正在保存數據...");
    }

}

ProxyFactory

/**
 * Cglib子類代理工廠
 * 對UserService在內存中動態構建一個子類對象
 */
public class ProxyFactory implements MethodInterceptor {

    /**
     * 目標對象
     */
    private Object target;

    public ProxyFactory(Object target) {
        this.target = target;
    }

    /**
     * 給目標對象創建一個代理對象
     */
    public Object getProxyInstance() {
        // 1.工具類
        Enhancer en = new Enhancer();
        // 2.設置父類
        en.setSuperclass(target.getClass());
        // 3.設置回調函數
        en.setCallback(this);
        // 4.創建子類(代理對象)
        return en.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("==========前置處理==========");

        // 執行目標對象的方法
        Object result = method.invoke(target, objects);

        System.out.println("==========後置處理==========");
        return result;
    }

}

Client

public class Client {

    public static void main(String[] args) {
        // 目標對象
        UserService service = new UserService();
        // 代理對象
        UserService proxyService = (UserService) new ProxyFactory(service).getProxyInstance();
        // 執行代理對象的方法
        proxyService.save();
    }

}

----------------輸出---------------
==========前置處理==========
正在保存數據...
==========後置處理==========

源碼

Click here


總結

由於JDK動態代理基於Java反射機制實現,必須要實現了接口的業務類才能用這種辦法生成代理對象,所以如果目標對象有實現接口,那麼就用JDK代理。
由於Cglib動態代理基於ASM機制實現,通過生成業務類的子類作爲代理類,所以如果目標對象沒有實現接口,用Cglib代理。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章