Java 動態代理詳解

微信原文:Java 動態代理詳解
博客原文:Java 動態代理詳解php

動態代理在Java中有着普遍的應用,好比Spring AOP、Hibernate數據查詢、測試框架的後端mock、RPC遠程調用、Java註解對象獲取、日誌、用戶鑑權、全局性異常處理、性能監控,甚至事務處理等。html

本文主要介紹Java中兩種常見的動態代理方式:JDK原生動態代理和CGLIB動態代理。java

因爲Java動態代理與java反射機制關係緊密,請讀者確保已經瞭解了Java反射機制,可參考上一篇文章《Java反射機制詳解面試

代理模式

本文將介紹的Java動態代理與設計模式中的代理模式有關,什麼是代理模式呢?數據庫

代理模式:給某一個對象提供一個代理,並由代理對象來控制對真實對象的訪問。代理模式是一種結構型設計模式。segmentfault

代理模式角色分爲 3 種:後端

Subject(抽象主題角色):定義代理類和真實主題的公共對外方法,也是代理類代理真實主題的方法;設計模式

RealSubject(真實主題角色):真正實現業務邏輯的類;api

Proxy(代理主題角色):用來代理和封裝真實主題;數組

代理模式的結構比較簡單,其核心是代理類,爲了讓客戶端可以一致性地對待真實對象和代理對象,在代理模式中引入了抽象層

代理模式類圖

代理模式按照職責(使用場景)來分類,至少能夠分爲如下幾類:一、遠程代理。 二、虛擬代理。 三、Copy-on-Write 代理。 四、保護(Protect or Access)代理。 五、Cache代理。 六、防火牆(Firewall)代理。 七、同步化(Synchronization)代理。 八、智能引用(Smart Reference)代理等等。

若是根據字節碼的建立時機來分類,能夠分爲靜態代理和動態代理:

  • 所謂靜態也就是在程序運行前就已經存在代理類的字節碼文件,代理類和真實主題角色的關係在運行前就肯定了。
  • 而動態代理的源碼是在程序運行期間由JVM根據反射等機制動態的生成,因此在運行前並不存在代理類的字節碼文件

靜態代理

咱們先經過實例來學習靜態代理,而後理解靜態代理的缺點,再來學習本文的主角:動態代理

編寫一個接口 UserService ,以及該接口的一個實現類 UserServiceImpl

public interface UserService {
    public void select();   
    public void update();
}

public class UserServiceImpl implements UserService {  
    public void select() {  
        System.out.println("查詢 selectById");
    }
    public void update() {
        System.out.println("更新 update");
    }
}
複製代碼

咱們將經過靜態代理對 UserServiceImpl 進行功能加強,在調用 selectupdate 以前記錄一些日誌。寫一個代理類 UserServiceProxy,代理類須要實現 UserService

public class UserServiceProxy implements UserService {
    private UserService target; // 被代理的對象

    public UserServiceProxy(UserService target) {
        this.target = target;
    }
    public void select() {
        before();
        target.select();    // 這裏才實際調用真實主題角色的方法
        after();
    }
    public void update() {
        before();
        target.update();    // 這裏才實際調用真實主題角色的方法
        after();
    }

    private void before() {     // 在執行方法以前執行
        System.out.println(String.format("log start time [%s] ", new Date()));
    }
    private void after() {      // 在執行方法以後執行
        System.out.println(String.format("log end time [%s] ", new Date()));
    }
}
複製代碼

客戶端測試

public class Client1 {
    public static void main(String[] args) {
        UserService userServiceImpl = new UserServiceImpl();
        UserService proxy = new UserServiceProxy(userServiceImpl);

        proxy.select();
        proxy.update();
    }
}
複製代碼

輸出

log start time [Thu Dec 20 14:13:25 CST 2018] 
查詢 selectById
log end time [Thu Dec 20 14:13:25 CST 2018] 
log start time [Thu Dec 20 14:13:25 CST 2018] 
更新 update
log end time [Thu Dec 20 14:13:25 CST 2018] 
複製代碼

經過靜態代理,咱們達到了功能加強的目的,並且沒有侵入原代碼,這是靜態代理的一個優勢。

靜態代理的缺點

雖然靜態代理實現簡單,且不侵入原代碼,可是,當場景稍微複雜一些的時候,靜態代理的缺點也會暴露出來。

一、 當須要代理多個類的時候,因爲代理對象要實現與目標對象一致的接口,有兩種方式:

  • 只維護一個代理類,由這個代理類實現多個接口,可是這樣就致使代理類過於龐大
  • 新建多個代理類,每一個目標對象對應一個代理類,可是這樣會產生過多的代理類

二、 當接口須要增長、刪除、修改方法的時候,目標對象與代理類都要同時修改,不易維護

如何改進?

固然是讓代理類動態的生成啦,也就是動態代理。

爲何類能夠動態的生成?

這就涉及到Java虛擬機的類加載機制了,推薦翻看《深刻理解Java虛擬機》7.3節 類加載的過程。

Java虛擬機類加載過程主要分爲五個階段:加載、驗證、準備、解析、初始化。其中加載階段須要完成如下3件事情:

  1. 經過一個類的全限定名來獲取定義此類的二進制字節流
  2. 將這個字節流所表明的靜態存儲結構轉化爲方法區的運行時數據結構
  3. 在內存中生成一個表明這個類的 java.lang.Class 對象,做爲方法區這個類的各類數據訪問入口

因爲虛擬機規範對這3點要求並不具體,因此實際的實現是很是靈活的,關於第1點,獲取類的二進制字節流(class字節碼)就有不少途徑:

  • 從ZIP包獲取,這是JAR、EAR、WAR等格式的基礎
  • 從網絡中獲取,典型的應用是 Applet
  • 運行時計算生成,這種場景使用最多的是動態代理技術,在 java.lang.reflect.Proxy 類中,就是用了 ProxyGenerator.generateProxyClass 來爲特定接口生成形式爲 *$Proxy 的代理類的二進制字節流
  • 由其它文件生成,典型應用是JSP,即由JSP文件生成對應的Class類
  • 從數據庫中獲取等等

因此,動態代理就是想辦法,根據接口或目標對象,計算出代理類的字節碼,而後再加載到JVM中使用。可是如何計算?如何生成?狀況也許比想象的複雜得多,咱們須要藉助現有的方案。

常見的字節碼操做類庫

這裏有一些介紹:java-source.net/open-source…

  • Apache BCEL (Byte Code Engineering Library):是Java classworking普遍使用的一種框架,它能夠深刻到JVM彙編語言進行類操做的細節。
  • ObjectWeb ASM:是一個Java字節碼操做框架。它能夠用於直接以二進制形式動態生成stub根類或其餘代理類,或者在加載時動態修改類。
  • CGLIB(Code Generation Library):是一個功能強大,高性能和高質量的代碼生成庫,用於擴展JAVA類並在運行時實現接口。
  • Javassist:是Java的加載時反射系統,它是一個用於在Java中編輯字節碼的類庫; 它使Java程序可以在運行時定義新類,並在JVM加載以前修改類文件。
  • ...

實現動態代理的思考方向

爲了讓生成的代理類與目標對象(真實主題角色)保持一致性,從如今開始將介紹如下兩種最多見的方式:

  1. 經過實現接口的方式 -> JDK動態代理
  2. 經過繼承類的方式 -> CGLIB動態代理

注:使用ASM對使用者要求比較高,使用Javassist會比較麻煩

JDK動態代理

JDK動態代理主要涉及兩個類:java.lang.reflect.Proxyjava.lang.reflect.InvocationHandler,咱們仍然經過案例來學習

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

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Date;

public class LogHandler implements InvocationHandler {
    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);  // 調用 target 的 method 方法
        after();
        return result;  // 返回方法的執行結果
    }
    // 調用invoke方法以前執行
    private void before() {
        System.out.println(String.format("log start time [%s] ", new Date()));
    }
    // 調用invoke方法以後執行
    private void after() {
        System.out.println(String.format("log end time [%s] ", new Date()));
    }
}
複製代碼

編寫客戶端,獲取動態生成的代理類的對象須藉助 Proxy 類的 newProxyInstance 方法,具體步驟可見代碼和註釋

import proxy.UserService;
import proxy.UserServiceImpl;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class Client2 {
    public static void main(String[] args) throws IllegalAccessException, InstantiationException {
        // 設置變量能夠保存動態代理類,默認名稱以 $Proxy0 格式命名
        // System.getProperties().setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        // 1. 建立被代理的對象,UserService接口的實現類
        UserServiceImpl userServiceImpl = new UserServiceImpl();
        // 2. 獲取對應的 ClassLoader
        ClassLoader classLoader = userServiceImpl.getClass().getClassLoader();
        // 3. 獲取全部接口的Class,這裏的UserServiceImpl只實現了一個接口UserService,
        Class[] interfaces = userServiceImpl.getClass().getInterfaces();
        // 4. 建立一個將傳給代理類的調用請求處理器,處理全部的代理對象上的方法調用
        // 這裏建立的是一個自定義的日誌處理器,須傳入實際的執行對象 userServiceImpl
        InvocationHandler logHandler = new LogHandler(userServiceImpl);
        /* 5.根據上面提供的信息,建立代理對象 在這個過程當中, a.JDK會經過根據傳入的參數信息動態地在內存中建立和.class 文件等同的字節碼 b.而後根據相應的字節碼轉換成對應的class, c.而後調用newInstance()建立代理實例 */
        UserService proxy = (UserService) Proxy.newProxyInstance(classLoader, interfaces, logHandler);
        // 調用代理的方法
        proxy.select();
        proxy.update();
        
        // 保存JDK動態代理生成的代理類,類名保存爲 UserServiceProxy
        // ProxyUtils.generateClassFile(userServiceImpl.getClass(), "UserServiceProxy");
    }
}
複製代碼

運行結果

log start time [Thu Dec 20 16:55:19 CST 2018] 
查詢 selectById
log end time [Thu Dec 20 16:55:19 CST 2018] 
log start time [Thu Dec 20 16:55:19 CST 2018] 
更新 update
log end time [Thu Dec 20 16:55:19 CST 2018] 
複製代碼

InvocationHandler 和 Proxy 的主要方法介紹以下:

java.lang.reflect.InvocationHandler

Object invoke(Object proxy, Method method, Object[] args) 定義了代理對象調用方法時但願執行的動做,用於集中處理在動態代理類對象上的方法調用

java.lang.reflect.Proxy

static InvocationHandler getInvocationHandler(Object proxy) 用於獲取指定代理對象所關聯的調用處理器

static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces) 返回指定接口的代理類

static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 構造實現指定接口的代理類的一個新實例,全部方法會調用給定處理器對象的 invoke 方法

static boolean isProxyClass(Class<?> cl) 返回 cl 是否爲一個代理類

代理類的調用過程

生成的代理類到底長什麼樣子呢?藉助下面的工具類,把代理類保存下來再探個究竟
(經過設置環境變量sun.misc.ProxyGenerator.saveGeneratedFiles=true也能夠保存代理類)

import sun.misc.ProxyGenerator;
import java.io.FileOutputStream;
import java.io.IOException;

public class ProxyUtils {
    /** * 將根據類信息動態生成的二進制字節碼保存到硬盤中,默認的是clazz目錄下 * params: clazz 須要生成動態代理類的類 * proxyName: 爲動態生成的代理類的名稱 */
    public static void generateClassFile(Class clazz, String proxyName) {
        // 根據類信息和提供的代理類名稱,生成字節碼
        byte[] classFile = ProxyGenerator.generateProxyClass(proxyName, clazz.getInterfaces());
        String paths = clazz.getResource(".").getPath();
        System.out.println(paths);
        FileOutputStream out = null;
        try {
            //保留到硬盤中
            out = new FileOutputStream(paths + proxyName + ".class");
            out.write(classFile);
            out.flush();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
複製代碼

而後在 Client2 測試類的main的最後面加入一行代碼

// 保存JDK動態代理生成的代理類,類名保存爲 UserServiceProxy
ProxyUtils.generateClassFile(userServiceImpl.getClass(), "UserServiceProxy");
複製代碼

IDEA 再次運行以後就能夠在 target 的類路徑下找到 UserServiceProxy.class,雙擊後IDEA的反編譯插件會將該二進制class文件

JDK 動態代理生成的代理類

UserServiceProxy 的代碼以下所示:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import proxy.UserService;

public final class UserServiceProxy extends Proxy implements UserService {
    private static Method m1;
    private static Method m2;
    private static Method m4;
    private static Method m0;
    private static Method m3;

    public UserServiceProxy(InvocationHandler var1) throws {
        super(var1);
    }

    public final boolean equals(Object var1) throws {
        // 省略...
    }

    public final String toString() throws {
        // 省略...
    }

    public final void select() throws {
        try {
            super.h.invoke(this, m4, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws {
        // 省略...
    }

    public final void update() throws {
        try {
            super.h.invoke(this, m3, (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");
            m4 = Class.forName("proxy.UserService").getMethod("select");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            m3 = Class.forName("proxy.UserService").getMethod("update");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}
複製代碼

從 UserServiceProxy 的代碼中咱們能夠發現:

  • UserServiceProxy 繼承了 Proxy 類,而且實現了被代理的全部接口,以及equals、hashCode、toString等方法
  • 因爲 UserServiceProxy 繼承了 Proxy 類,因此每一個代理類都會關聯一個 InvocationHandler 方法調用處理器
  • 類和全部方法都被 public final 修飾,因此代理類只可被使用,不能夠再被繼承
  • 每一個方法都有一個 Method 對象來描述,Method 對象在static靜態代碼塊中建立,以 m + 數字 的格式命名
  • 調用方法的時候經過 super.h.invoke(this, m1, (Object[])null); 調用,其中的 super.h.invoke 其實是在建立代理的時候傳遞給 Proxy.newProxyInstance 的 LogHandler 對象,它繼承 InvocationHandler 類,負責實際的調用處理邏輯

而 LogHandler 的 invoke 方法接收到 method、args 等參數後,進行一些處理,而後經過反射讓被代理的對象 target 執行方法

@Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object result = method.invoke(target, args);       // 調用 target 的 method 方法
        after();
        return result;  // 返回方法的執行結果
    }
複製代碼

JDK動態代理執行方法調用的過程簡圖以下:

JDK動態代理執行方法調用過程

代理類的調用過程相信你們都明瞭了,而關於Proxy的源碼解析,還請你們另外查閱其餘文章或者直接看源碼

CGLIB動態代理

maven引入CGLIB包,而後編寫一個UserDao類,它沒有接口,只有兩個方法,select() 和 update()

public class UserDao {
    public void select() {
        System.out.println("UserDao 查詢 selectById");
    }
    public void update() {
        System.out.println("UserDao 更新 update");
    }
}
複製代碼

編寫一個 LogInterceptor ,繼承了 MethodInterceptor,用於方法的攔截回調

import java.lang.reflect.Method;
import java.util.Date;

public class LogInterceptor implements MethodInterceptor {
    /** * @param object 表示要進行加強的對象 * @param method 表示攔截的方法 * @param objects 數組表示參數列表,基本數據類型須要傳入其包裝類型,如int-->Integer、long-Long、double-->Double * @param methodProxy 表示對方法的代理,invokeSuper方法表示對被代理對象方法的調用 * @return 執行結果 * @throws Throwable */
    @Override
    public Object intercept(Object object, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        before();
        Object result = methodProxy.invokeSuper(object, objects);   // 注意這裏是調用 invokeSuper 而不是 invoke,不然死循環,methodProxy.invokesuper執行的是原始類的方法,method.invoke執行的是子類的方法
        after();
        return result;
    }
    private void before() {
        System.out.println(String.format("log start time [%s] ", new Date()));
    }
    private void after() {
        System.out.println(String.format("log end time [%s] ", new Date()));
    }
}
複製代碼

測試

import net.sf.cglib.proxy.Enhancer;

public class CglibTest {
    public static void main(String[] args) {
        DaoProxy daoProxy = new DaoProxy(); 
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Dao.class);  // 設置超類,cglib是經過繼承來實現的
        enhancer.setCallback(daoProxy);

        Dao dao = (Dao)enhancer.create();   // 建立代理類
        dao.update();
        dao.select();
    }
}
複製代碼

運行結果

log start time [Fri Dec 21 00:06:40 CST 2018] 
UserDao 查詢 selectById
log end time [Fri Dec 21 00:06:40 CST 2018] 
log start time [Fri Dec 21 00:06:40 CST 2018] 
UserDao 更新 update
log end time [Fri Dec 21 00:06:40 CST 2018] 
複製代碼

還能夠進一步多個 MethodInterceptor 進行過濾篩選

public class LogInterceptor2 implements MethodInterceptor {
    @Override
    public Object intercept(Object object, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        before();
        Object result = methodProxy.invokeSuper(object, objects);
        after();
        return result;
    }
    private void before() {
        System.out.println(String.format("log2 start time [%s] ", new Date()));
    }
    private void after() {
        System.out.println(String.format("log2 end time [%s] ", new Date()));
    }
}

// 回調過濾器: 在CGLib回調時能夠設置對不一樣方法執行不一樣的回調邏輯,或者根本不執行回調。
public class DaoFilter implements CallbackFilter {
    @Override
    public int accept(Method method) {
        if ("select".equals(method.getName())) {
            return 0;   // Callback 列表第1個攔截器
        }
        return 1;   // Callback 列表第2個攔截器,return 2 則爲第3個,以此類推
    }
}
複製代碼

再次測試

public class CglibTest2 {
    public static void main(String[] args) {
        LogInterceptor logInterceptor = new LogInterceptor();
        LogInterceptor2 logInterceptor2 = new LogInterceptor2();
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(UserDao.class);   // 設置超類,cglib是經過繼承來實現的
        enhancer.setCallbacks(new Callback[]{logInterceptor, logInterceptor2, NoOp.INSTANCE});   // 設置多個攔截器,NoOp.INSTANCE是一個空攔截器,不作任何處理
        enhancer.setCallbackFilter(new DaoFilter());

        UserDao proxy = (UserDao) enhancer.create();   // 建立代理類
        proxy.select();
        proxy.update();
    }
}
複製代碼

運行結果

log start time [Fri Dec 21 00:22:39 CST 2018] 
UserDao 查詢 selectById
log end time [Fri Dec 21 00:22:39 CST 2018] 
log2 start time [Fri Dec 21 00:22:39 CST 2018] 
UserDao 更新 update
log2 end time [Fri Dec 21 00:22:39 CST 2018] 
複製代碼

CGLIB 建立動態代理類的模式是:

  1. 查找目標類上的全部非final 的public類型的方法定義;
  2. 將這些方法的定義轉換成字節碼;
  3. 將組成的字節碼轉換成相應的代理的class對象;
  4. 實現 MethodInterceptor接口,用來處理對代理類上全部方法的請求

JDK動態代理與CGLIB動態代理對比

JDK動態代理:基於Java反射機制實現,必需要實現了接口的業務類才能用這種辦法生成代理對象。

cglib動態代理:基於ASM機制實現,經過生成業務類的子類做爲代理類。

JDK Proxy 的優點:

  • 最小化依賴關係,減小依賴意味着簡化開發和維護,JDK 自己的支持,可能比 cglib 更加可靠。
  • 平滑進行 JDK 版本升級,而字節碼類庫一般須要進行更新以保證在新版 Java 上可以使用。
  • 代碼實現簡單。

基於相似 cglib 框架的優點:

  • 無需實現接口,達到代理類無侵入
  • 只操做咱們關心的類,而沒必要爲其餘相關類增長工做量。
  • 高性能

面試題

來源於網上,用於幫助理解和掌握,歡迎補充

描述動態代理的幾種實現方式?分別說出相應的優缺點

代理能夠分爲 "靜態代理" 和 "動態代理",動態代理又分爲 "JDK動態代理" 和 "CGLIB動態代理" 實現。

靜態代理:代理對象和實際對象都繼承了同一個接口,在代理對象中指向的是實際對象的實例,這樣對外暴露的是代理對象而真正調用的是 Real Object

  • 優勢:能夠很好的保護實際對象的業務邏輯對外暴露,從而提升安全性。
  • 缺點:不一樣的接口要有不一樣的代理類實現,會很冗餘

JDK 動態代理

  • 爲了解決靜態代理中,生成大量的代理類形成的冗餘;

  • JDK 動態代理只須要實現 InvocationHandler 接口,重寫 invoke 方法即可以完成代理的實現,

  • jdk的代理是利用反射生成代理類 Proxyxx.class 代理類字節碼,並生成對象

  • jdk動態代理之因此只能代理接口是由於代理類自己已經extends了Proxy,而java是不容許多重繼承的,可是容許實現多個接口

  • 優勢:解決了靜態代理中冗餘的代理實現類問題。

  • 缺點:JDK 動態代理是基於接口設計實現的,若是沒有接口,會拋異常。

CGLIB 代理

  • 因爲 JDK 動態代理限制了只能基於接口設計,而對於沒有接口的狀況,JDK方式解決不了;

  • CGLib 採用了很是底層的字節碼技術,其原理是經過字節碼技術爲一個類建立子類,並在子類中採用方法攔截的技術攔截全部父類方法的調用,順勢織入橫切邏輯,來完成動態代理的實現。

  • 實現方式實現 MethodInterceptor 接口,重寫 intercept 方法,經過 Enhancer 類的回調方法來實現。

  • 可是CGLib在建立代理對象時所花費的時間卻比JDK多得多,因此對於單例的對象,由於無需頻繁建立對象,用CGLib合適,反之,使用JDK方式要更爲合適一些。

  • 同時,因爲CGLib因爲是採用動態建立子類的方法,對於final方法,沒法進行代理。

  • 優勢:沒有接口也能實現動態代理,並且採用字節碼加強技術,性能也不錯。

  • 缺點:技術實現相對難理解些。

CGlib 對接口實現代理?

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import proxy.UserService;
import java.lang.reflect.Method;

/** * 建立代理類的工廠 該類要實現 MethodInterceptor 接口。 * 該類中完成三樣工做: * (1)聲明目標類的成員變量,並建立以目標類對象爲參數的構造器。用於接收目標對象 * (2)定義代理的生成方法,用於建立代理對象。方法名是任意的。代理對象即目標類的子類 * (3)定義回調接口方法。對目標類的加強這在這裏完成 */
public class CGLibFactory implements MethodInterceptor {
    // 聲明目標類的成員變量
    private UserService target;

    public CGLibFactory(UserService target) {
        this.target = target;
    }
    // 定義代理的生成方法,用於建立代理對象
    public UserService myCGLibCreator() {
        Enhancer enhancer = new Enhancer();
        // 爲代理對象設置父類,即指定目標類
        enhancer.setSuperclass(UserService.class);
        /** * 設置回調接口對象 注意,只因此在setCallback()方法中能夠寫上this, * 是由於MethodIntecepter接口繼承自Callback,是其子接口 */
        enhancer.setCallback(this);
        return (UserService) enhancer.create();// create用以生成CGLib代理對象
    }
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("start invoke " + method.getName());
        Object result = method.invoke(target, args);
        System.out.println("end invoke " + method.getName());
        return result;
    }
}


複製代碼

參考:
《Java核心技術》卷1
《深刻理解Java虛擬機》7.3
java docs: docs.oracle.com/javase/8/do…
Java三種代理模式:靜態代理、動態代理和cglib代理
描述動態代理的幾種實現方式 分別說出相應的優缺點
JDK動態代理詳解
Java動態代理機制詳解(JDK 和CGLIB,Javassist,ASM)
靜態代理和動態代理的理解

後記

歡迎評論、轉發、分享,您的支持是我最大的動力

更多內容可訪問個人我的博客:laijianfeng.org

關注【小旋鋒】微信公衆號,及時接收博文推送

關注_小旋鋒_微信公衆號
相關文章
相關標籤/搜索