JDK靜態代理、動態代理和CGLIB動態代理

語言: CN / TW / HK

theme: cyanosis

參考資料

代理

代理模式(Proxy Pattern)給某一個物件提供一個代理,並由代理物件控制原物件的引用。代理物件在客戶端和目標物件之間起到中介作用。

代理模式結構

代理模式優缺點

優點 1. 代理模式能將代理物件與真實被呼叫的目標物件分離 2. 一定程度上降低了系統的耦合度,擴充套件性好 3. 可以起到保護目標物件的作用。 4. 可以對目標物件的功能增強

缺點 1. 代理模式會造成系統設計中類的數量增加 2. 在客戶端和目標物件增加一個代理物件,會造成請求處理速度變慢

代理模式分類

代理模式可以分為靜態代理和動態代理兩種型別,而動態代理中又分為 JDK 動態代理和 CGLIB 代理兩種。 1. 靜態代理:程式碼編譯時就已實現,編譯完成後,代理類是一個實際的 class 檔案 2. 動態代理:在執行時動態生成的,編譯完成後沒有實際的 class 檔案,而是在執行時動態生成類位元組碼,並載入到 JVM 中 * JDK 代理 * CGLIB 代理

3種代理方式的對比

| 代理方式 | 實現 | 優點 | 缺點 | 特點 | |---------|--------------------------------|------------------|----------------|------------| | JDK 靜態代理 | 代理類與委託類實現同一介面。在代理類中需要硬編碼介面 | 實現簡單,容易理解 | 代理類需要硬編碼介面,在實際應用中可能會導致重複編碼,浪費儲存空間並且效率較低 | | | JDK 動態代理 | 代理類與委託類實現同一介面。代理類要實現 InvocationHandler 介面並重寫 invoke 方法。invoke 方法中可以對委託方法進行增強處理 | 不需要硬編碼介面,程式碼複用率高 | 只能夠代理實現了介面的委託類 | 底層使用反射機制進行方法的呼叫 | | CGLIB 動態代理 | 代理類將委託類作為自己的父類併為其中的非 final 委託方法建立兩個方法,一個是與委託方法簽名相同的方法,它在方法中會通過 super 呼叫委託方法;另一個是代理類獨有的方法。在代理方法中,它會判斷是否存在實現了MethodInterceptor 介面的物件,若存在則將呼叫 intercept 方法對委託方法進行代理 | 可以在執行時對類或者是介面進行增強操作,且委託類無需實現介面 | 不能對 final 類以及 final 方法進行代理 | 底層將方法全部存入一個數組中,通過陣列索引直接進行方法呼叫 |

靜態代理和動態代理的對比

  • 靜態代理只能通過手動完成代理操作,如果被代理類增加新的方法,代理類需要同步新增,違背開閉原則。
  • 動態代理採用在執行時動態生成程式碼的方式,取消了對被代理類的擴充套件限制,遵循開閉原則。
  • 若動態代理要對目標類的增強邏輯擴充套件,結合策略模式,只需要新增策略類便可完成,無需修改代理類的程式碼。

CGLIB 動態代理比 JDK 動態代理速度快?

  • CGLIB 動態代理中,通過位元組碼技術(使用了位元組碼處理框架 ASM)為一個類建立子類,並在子類中通過 FastClass 機制,採用方法攔截的技術攔截所有父類方法的呼叫,順勢織入橫切邏輯。
  • JDK 動態代理中,通過反射技術實現對委託方法的呼叫。
  • 在 JDK 6 之前,CGLIB 動態代理比使用 Java 反射效率要高
  • 在 JDK 6、7、8 版本中,逐步對 JDK 動態代理優化之後,在呼叫次數較少的情況下,JDK 代理效率高於 CGLIB 代理效率。
  • 從 JDK 8 版本開始,每一次 JDK 版本升級,JDK 代理效率都得到較大提升,效率已經高於 CGLIB 代理。

Spring AOP 中是使用的 JDK 代理還是 CGLIB 代理?

  • 當 Bean 實現介面時,Spring 就會用 JDK 的動態代理。
  • 當 Bean 沒有實現介面時,Spring 使用 CGLIB 實現。
  • 若兩種方式均可,則首選 JDK 做動態代理(效率更高)。不過也可以強制使用 CGLIB。

靜態代理

結合「代理模式結構」章節的結構圖,對靜態代理的實現進行說明 1. 對「服務介面」,建立一個「服務類」實現該介面。 2. 同時建立一個「代理類」也實現該介面,代理類中持有一個指向「服務物件」的引用成員變數。通常情況下,「代理類」會對其「服務物件」的整個生命週期進行管理。 3. 「客戶端」通過「代理類」來呼叫「服務物件」實現的介面方法。

```java //1.「服務介面」 public interface UserDao {
void save();
}

//2.「服務類」 public class UserDaoImpl implements UserDao { @Override public void save() { System.out.println("正在儲存使用者..."); } }

//3.「代理類」 public class TransactionHandler implements UserDao { // 目標代理物件 // 2.「服務物件」 private UserDao target;

//構造代理物件時傳入目標物件
public TransactionHandler(UserDao target) {
    this.target = target;  //也可以延遲初始化 如在呼叫save方法時才建立target物件
}

@Override
public void save() {
    //呼叫目標方法前的處理
    System.out.println("開啟事務控制...");
    //呼叫目標物件的方法
    target.save();
    //呼叫目標方法後的處理
    System.out.println("關閉事務控制...");
}

}

// 4. 「客戶端」 public class Main { public static void main(String[] args) { //新建目標物件 UserDaoImpl target = new UserDaoImpl();

    //建立代理物件, 並使用介面對其進行引用
    UserDao userDao = new TransactionHandler(target);

    //針對介面進行呼叫
    userDao.save();
}

} ```

靜態代理只能為一個類服務,如果需要代理的類很多,那麼就需要編寫大量的代理類,比較繁瑣。

JDK動態代理

原理

在 JDK 動態代理中,每一個動態代理類都必須實現 InvocationHandler 介面,並且每個動態代理類都關聯了一個 handler。當我們通過代理物件呼叫一個方法的時候,這個方法的呼叫,就會被轉發為由 InvocationHandler 介面的 invoke 方法來進行呼叫。invoke 方法會根據傳入的代理物件、方法名稱以及引數決定呼叫代理的哪個方法。詳情見下 InvocationHandler 類的原始碼註釋。

```java

/* * InvocationHandler is the interface implemented by * the invocation handler of a proxy instance. * * Each proxy instance has an associated invocation handler. * When a method is invoked on a proxy instance, the method * invocation is encoded and dispatched to the invoke * method of its invocation handler. * * @author Peter Jones * @see Proxy * @since 1.3 / public interface InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;

} ```

使用步驟

使用 JDK 動態代理的 5 大步驟如下 1. 為動態代理類實現 InvocationHandler 介面並重寫 invoke 方法,即為動態代理類關聯一個自定義的 handler。 2. 通過 Proxy.getProxyClass 獲得動態代理類。 3. 通過反射機制獲得代理類的構造方法,方法簽名為 getConstructor(InvocationHandler.class)。 4. 通過建構函式獲得代理物件,並將自定義的 InvocationHandler 例項物件作為引數傳入。 5. 通過代理物件呼叫目標方法。

Java 的 Proxy 類提供了一個 Proxy.newProxyInstance() 方法,封裝了上面的第 2~4 步的工作(詳情見下 Proxy 類的原始碼註釋),故步驟可精簡為 3 步 1. 為動態代理類實現 InvocationHandler 介面並重寫 invoke 方法,即為動態代理類關聯一個自定義的 handler。 2. 使用 Proxy.newProxyInstance() 方法獲得代理物件 3. 通過代理物件呼叫目標方法。

```java /* * Proxy provides static methods for creating dynamic proxy * classes and instances, and it is also the superclass of all * dynamic proxy classes created by those methods. * * To create a proxy for some interface Foo: * * InvocationHandler handler = new MyInvocationHandler(...); * Class<?> proxyClass = Proxy.getProxyClass(Foo.class.getClassLoader(), * Foo.class); * Foo f = (Foo) proxyClass.getConstructor(InvocationHandler.class). * newInstance(handler); * * or more simply: * * Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(), * new Class<?>[] { Foo.class }, * handler); * / public class Proxy implements java.io.Serializable { @CallerSensitive public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { // ... } }

```

使用示例

下面給出一個 JDK 動態代理的示例。

```java //1.「服務介面」 public interface IHello { void sayHello(); }

//2.「服務類」 public class HelloImpl implements IHello { @Override public void sayHello() { System.out.println("Hello world!"); } } ```

```java import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method;

// 為動態代理類關聯一個自定義的 InvocationHandler public class MyInvocationHandler implements InvocationHandler {

/** 目標物件 */
private Object target;

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

//重寫invoke方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    System.out.println("------插入前置通知程式碼-------------");
    // 執行相應的目標方法
    Object rs = method.invoke(target,args);
    System.out.println("------插入後置處理程式碼-------------");
    return rs;
}

} ```

```java import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Proxy;

public class MyProxyTest { public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException { // =========================第一種========================== // 1、生成$Proxy0的class檔案 System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); // 2、獲取動態代理類 Class proxyClazz = Proxy.getProxyClass(IHello.class.getClassLoader(),IHello.class); // 3、獲得代理類的建構函式,並傳入引數型別InvocationHandler.class Constructor constructor = proxyClazz.getConstructor(InvocationHandler.class); // 4、通過建構函式來建立動態代理物件,將自定義的InvocationHandler例項傳入 IHello iHello1 = (IHello) constructor.newInstance(new MyInvocationHandler(new HelloImpl())); // 5、通過代理物件呼叫目標方法 iHello1.sayHello();

    // ==========================第二種=============================
    /**
     * Proxy類中還有個將2~4步驟封裝好的簡便方法來建立動態代理物件,
     *其方法簽名為:newProxyInstance(ClassLoader loader,Class<?>[] instance, InvocationHandler h)
     */
    IHello  iHello2 = (IHello) Proxy.newProxyInstance(IHello.class.getClassLoader(), // 載入介面的類載入器
            new Class[]{IHello.class}, // 一組介面
            new MyInvocationHandler(new HelloImpl())); // 自定義的InvocationHandler
    iHello2.sayHello();
}

} ```

JDK靜態代理和JDK動態代理的對比

相同點 1. JDK 靜態代理和 JDK 動態代理,兩者在使用時都需要建立代理類,並且代理類都要實現對應的介面。

不同點 1. JDK 靜態代理是通過直接編碼建立的,程式碼編譯前,代理類就需要實現和被代理類相同的介面 2. JDK 動態代理是利用「反射機制」在執行時建立代理類的。 3. JDK 動態代理中,動態代理類需要實現 InvocationHandler 介面,並重寫 invoke 方法。

CGLIB動態代理

原理

CGLIB 動態代理中,通過位元組碼技術(使用了位元組碼處理框架 ASM)為一個類建立子類,並在子類中採用方法攔截的技術攔截所有父類方法的呼叫,順勢織入橫切邏輯。

使用步驟

使用 CGLIB 動態代理的步驟如下 1. CGLIB動態代理中,要求代理類必須要實現 MethodInterceptor 介面並重寫 intercept()。 2. 通過 CGLIB 動態代理,獲取代理物件。 3. 通過代理物件呼叫目標方法時,對目標方法的呼叫,將被轉發到 MethodInterceptor 介面的 intercept() 方法來進行呼叫。在 intercept() 方法中,我們除了會呼叫委託方法外,還會進行一些增強操作,如日誌的記錄,呼叫量的監控等。

需要注意的是

  • CGLIB 動態代理中,代理類是繼承了被代理類(或服務類),即 enhancer.setSuperclass(Service.class)。所以,被代理類的final 方法或 final 類,不能被代理類繼承,也就不能通過代理的方式呼叫。
  • 代理類將委託類作為自己的父類,併為其中的非 final 委託方法建立兩個方法,一個是與委託方法簽名相同的方法,它在方法中會通過 super 呼叫委託方法;另一個是代理類獨有的方法。在代理方法中,它會判斷是否存在實現了 MethodInterceptor 介面的物件,若存在則將呼叫 intercept 方法對委託方法進行代理。
使用示例

```java public class HelloService {

public HelloService() {
    System.out.println("HelloService構造");
}

/**
 * final方法不能被子類覆蓋
 * Cglib 是無法代理 final 修飾的方法的
 */
final public String sayOthers(String name) {
    System.out.println("HelloService:sayOthers>>"+name);
    return null;
}

public void sayHello() {
    System.out.println("HelloService:sayHello");
}

} ```

```java import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/* * 自定義MethodInterceptor / public class MyMethodInterceptor implements MethodInterceptor{

/**
 * sub:cglib生成的代理物件
 * method:被代理物件方法
 * objects:方法入參
 * methodProxy: 代理方法
 */
@Override
public Object intercept(Object sub, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
    System.out.println("======插入前置通知======");
    Object object = methodProxy.invokeSuper(sub, objects);
    System.out.println("======插入後者通知======");
    return object;
}

} ```

```java import net.sf.cglib.core.DebuggingClassWriter; import net.sf.cglib.proxy.Enhancer;

public class Client { public static void main(String[] args) { // 代理類class檔案存入本地磁碟方便我們反編譯檢視原始碼 System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/Users/lbs/CodeSpace"); // 通過CGLIB動態代理獲取代理物件的過程 Enhancer enhancer = new Enhancer(); // 設定enhancer物件的父類 // 代理類是繼承了服務類 !!! enhancer.setSuperclass(HelloService.class); // 設定enhancer的回撥物件 enhancer.setCallback(new MyMethodInterceptor()); // 建立代理物件 HelloService proxy= (HelloService)enhancer.create(); // 通過代理物件呼叫目標方法 proxy.sayHello(); } }

```

FastClass機制

CGLIB 動態代理中,方法的呼叫並不是通過反射來完成的,而是通過 FastClass 機制來實現對被攔截方法的呼叫。

FastClass 機制中,不使用反射類(ConstructorMethod)來呼叫委託類的方法,而是動態生成一個新的類(繼承 FastClass),併為委託類的方法呼叫語句建立索引,使用者根據方法簽名(方法名 + 引數型別)得到索引值,再通過索引值呼叫相應的方法。

下面給出一個分別使用反射技術和 FastClass 機制呼叫委託方法的示例。

  1. 委託類

```java public class DelegateClass {

public DelegateClass() {
}

public DelegateClass(String string) {
}

public boolean add(String string, int i) {
    System.out.println("This is add method: " + string + ", " + i);
    return true;
}

public void update() {
    System.out.println("This is update method");
}

} ```

  1. 通過反射技術呼叫委託方法

```java // Java Reflect public static void reflectTest() throws Exception {

Class delegateClass = DelegateClass.class;

// 反射構造類
Constructor delegateConstructor = delegateClass.getConstructor(String.class);
// 建立委託類例項
DelegateClass delegateInstance = (DelegateClass) delegateConstructor.newInstance("Tom");

// 反射方法類
Method addMethod = delegateClass.getMethod("add", String.class, int.class);
// 呼叫方法
addMethod.invoke(delegateInstance, "Tom", 30);

Method updateMethod = delegateClass.getMethod("update");
updateMethod.invoke(delegateInstance);

} ```

  1. 通過 FastClass 機制呼叫委託方法

```java // FastClass public static void fastClassTest() throws Exception { //設定 CGLIB 生成的代理類檔案存入本地磁碟,方便我們反編譯檢視原始碼 System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/Users/lbs/CodeSpace");

// FastClass動態子類例項
FastClass fastClass = FastClass.create(DelegateClass.class);

// 建立委託類例項
DelegateClass fastInstance = (DelegateClass) fastClass.newInstance(
    new Class[] {String.class}, new Object[]{"Jack"});

// 呼叫委託類方法
fastClass.invoke("add", new Class[]{ String.class, int.class}, fastInstance, 
    new Object[]{ "Jack", 25});

fastClass.invoke("update", new Class[]{}, fastInstance, new Object[]{});

} ```

  1. 下面看一下 FastClass 的原始碼

```java public abstract class FastClass{

// 委託類
private Class type;

// 子類訪問構造方法
protected FastClass() {}
protected FastClass(Class type) {
    this.type = type;
}

// 建立動態FastClass子類
public static FastClass create(Class type) {
    // Generator:子類生成器,繼承AbstractClassGenerator
    Generator gen = new Generator();
    gen.setType(type);
    gen.setClassLoader(type.getClassLoader());
    return gen.create();
}

/**
 * 呼叫委託類方法
 *
 * @param name 方法名
 * @param parameterTypes 方法引數型別
 * @param obj 委託類例項
 * @param args 方法引數物件
 */
public Object invoke(String name, Class[] parameterTypes, Object obj, Object[] args) {
    return invoke(getIndex(name, parameterTypes), obj, args);
}

/**
 * 根據方法描述符找到方法索引
 *
 * @param name 方法名
 * @param parameterTypes 方法引數型別
 */
public abstract int getIndex(String name, Class[] parameterTypes);


/**
 * 根據方法索引呼叫委託類方法
 *
 * @param index 方法索引
 * @param obj 委託類例項
 * @param args 方法引數物件
 */
public abstract Object invoke(int index, Object obj, Object[] args);

/**
 * 呼叫委託類構造方法
 * 
 * @param parameterTypes 構造方法引數型別
 * @param args 構造方法引數物件
 */
public Object newInstance(Class[] parameterTypes, Object[] args) throws {
    return newInstance(getIndex(parameterTypes), args);
}

/**
 * 根據構造方法描述符(引數型別)找到構造方法索引
 *
 * @param parameterTypes 構造方法引數型別
 */
public abstract int getIndex(Class[] parameterTypes);

/**
 * 根據構造方法索引呼叫委託類構造方法
 *
 * @param index 構造方法索引
 * @param args 構造方法引數物件
 */
public abstract Object newInstance(int index, Object[] args);

} ```

  1. 找到 CGLIB 生成的代理類檔案 DelegateClass$$FastClassByCGLIB$$4af5b667,反編譯檢視原始碼

```java public class DelegateClass$$FastClassByCGLIB$$4af5b667 extends FastClass {

/**
 * 動態子類構造方法
 */
public DelegateClass$$FastClassByCGLIB$$4af5b667(Class delegateClass) {
    super(delegateClass);
}

/**
 * 根據方法簽名得到方法索引
 *
 * @param name 方法名
 * @param parameterTypes 方法引數型別
 */
public int getIndex(String methodName, Class[] parameterTypes) {
    switch(methodName.hashCode()) {

        // 委託類方法add索引:0
        case 96417:
            if (methodName.equals("add")) {
                switch(parameterTypes.length) {
                    case 2:
                        if (parameterTypes[0].getName().equals("java.lang.String") && 
                            parameterTypes[1].getName().equals("int")) {
                            return 0;
                        }
                }
            }
            break;

        // 委託類方法update索引:1
        case -838846263:
            if (methodName.equals("update")) {
                switch(parameterTypes.length) {
                    case 0:
                        return 1;
                }
            }
            break;

        // Object方法equals索引:2
        case -1295482945:
            if (methodName.equals("equals")) {
                switch(parameterTypes.length) {
                    case 1:
                        if (parameterTypes[0].getName().equals("java.lang.Object")) {
                            return 2;
                        }
                }
            }
            break;

        // Object方法toString索引:3
        case -1776922004:
            if (methodName.equals("toString")) {
                switch(parameterTypes.length) {
                    case 0: return 3;
                }
            }
            break;

        // Object方法hashCode索引:4
        case 147696667:
            if (methodName.equals("hashCode")) {
                switch(parameterTypes.length) {
                    case 0:
                        return 4;
                }
            }
    }

    return -1;
}

/**
 * 根據方法索引呼叫委託類方法
 *
 * @param methodIndex 方法索引
 * @param delegateInstance 委託類例項
 * @param parameterValues 方法引數物件
 */
public Object invoke(int methodIndex, Object delegateInstance, Object[] parameterValues) {
    DelegateClass instance = (DelegateClass) delegateInstance;
    int index = methodIndex;
    try {
        switch(index) {
            case 0:
                // 委託類例項直接呼叫方法語句
                return new Boolean(instance.add((String)parameterValues[0], 
                        ((Number)parameterValues[1]).intValue()));
            case 1:
                instance.update();
                return null;
            case 2:
                return new Boolean(instance.equals(parameterValues[0]));
            case 3:
                return instance.toString();
            case 4:
                return new Integer(instance.hashCode());
        }
    } catch (Throwable t) {
        throw new InvocationTargetException(t);
    }

    throw new IllegalArgumentException("Cannot find matching method/constructor");
}

/**
 * 根據構造方法描述符(引數型別)找到構造方法索引
 *
 * @param parameterTypes 構造方法引數型別
 */
public int getIndex(Class[] parameterTypes) {
    switch(parameterTypes.length) {
        // 無參構造方法索引:0
        case 0:
            return 0;

        // 有參構造方法索引:1
        case 1:
            if (parameterTypes[0].getName().equals("java.lang.String")) {
                return 1;
            }
        default:
            return -1;
    }
}

/**
 * 根據構造方法索引呼叫委託類構造方法
 *
 * @param methodIndex 構造方法索引
 * @param parameterValues 構造方法引數物件
 */
public Object newInstance(int methodIndex, Object[] parameterValues) {
    // 建立委託類例項
    DelegateClass newInstance = new DelegateClass;
    DelegateClass newObject = newInstance;
    int index = methodIndex;
    try {
        switch(index) {
            // 呼叫構造方法(<init>)
            case 0:
                newObject.<init>();
                return newInstance;
            case 1:
                newObject.<init>((String)parameterValues[0]);
                return newInstance;
        }
    } catch (Throwable t) {
        throw new InvocationTargetException(t);
    }

    throw new IllegalArgumentException("Cannot find matching method/constructor");
}

public int getMaxIndex() {
    return 4;
}

} ```

JDK動態代理和CGLIB動態代理的對比
  1. JDK 動態代理時,需要委託類(被代理類)實現了一個介面。CGLIB 動態代理時,則無此要求,因為代理類是繼承的委託類。
  2. JDK 動態代理中,代理類通過 Java 反射技術呼叫委託方法。CGLIB 動態代理中,代理類通過 FastClass 機制,採用方法攔截的技術對委託方法進行呼叫。