動態代理的原理及其應用

pjmike_pj發表於2018-08-07

動態代理的介紹

動態代理是一種在執行時動態地建立代理物件,動態地處理代理方法呼叫的機制。

實際上它是一種代理機制。代理可以看做是對呼叫目標的一個封裝,直接通過代理來實現對目的碼的呼叫

與靜態代理的比較

靜態代理

提前寫好代理類,每個業務類都要對應一個代理類,不靈活

動態代理的原理及其應用

  • ISubject,該介面是被訪問者或者被訪問的物件
  • SubjectImpl,被訪問者的具體實現類
  • SubjectProxy,被訪問者或被訪問資源的代理實現類
  • Client:代表訪問者的抽象角色,client將會訪問Isubject型別的物件或者資源,通過代理類進行訪問。
  • 這裡的SubjectImpl和SubjectProxy都實現了ISubject的介面,SubjectProxy是將請求轉發給SubjectImpl,其內部有SubjectImpl的物件,並且可以新增一些限制

以上的圖解本質上就是代理模式的一個講解,適用於靜態代理和動態代理,區別在於代理物件和代理方法生成的時間和方式不同

動態代理是執行時自動生成代理物件,一個缺點是生成代理物件和呼叫代理方法需要耗費時間

動態代理的實現方式

動態代理主要有兩種實現方式,一種是JDK動態代理,一種是CGLIB位元組碼機制,當然還有Javassist或ASM庫,這兩個在CGLIB那塊一併介紹

JDK動態代理

說到JDK動態代理,就不得不提起反射機制,JDK動態代理就是通過反射機制實現的。

反射機制

反射就是通過Class類和java.lang.reflect類庫在執行時獲取某個類的資訊。比如通過java.lang.reflect類庫中Field,Method以及Constructor類就可以獲取類的相關資訊

JDK動態代理的實現

下面是通過反射機制實現JDK動態代理的一個簡單例子

/**
 * 動態代理的實現
 *
 * @author pjmike
 * @create 2018-08-04 17:42
 */
public class DynamicProxy {
    public static void main(String[] args) {
        IHelloImpl hello = new IHelloImpl();
        MyInvocationHandler handler = new MyInvocationHandler(hello);
        //獲取目標使用者的代理物件
        IHello proxyHello = (IHello) Proxy.newProxyInstance(IHelloImpl.class.getClassLoader(), IHelloImpl.class.getInterfaces(), handler);
        //呼叫代理方法
        proxyHello.sayHello();
    }
}

/**
 * 被訪問者介面
 */
interface IHello{
    void sayHello();
}

/**
 * 被訪問者的具體實現類
 */
class IHelloImpl implements IHello {

    @Override
    public void sayHello() {
        System.out.println("Hello World");
    }
}

class MyInvocationHandler implements InvocationHandler {
    private Object target;

    /**
     *
     * @param target 被代理的目標物件
     */
    public MyInvocationHandler(Object target) {
        this.target = target;
    }

    /**
     * 執行目標物件的方法
     *
     * @param proxy 代理物件
     * @param method 代理方法
     * @param args 方法引數
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("invoke method");
        System.out.println("Method name : "+method.getName());
        Object result = method.invoke(target, args);
        return result;
    }
}
複製程式碼

從上面的例子中可以看出,代理物件的生成是通過Proxy.newProxyInstance()來完成的

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
複製程式碼

newProxyInstance()方法主要以下三個引數

  • 類載入器(ClassLoader)用來載入動態代理類
  • 一個要實現介面的陣列,從這點就可以看出,要想使用JDK動態代理,必須要有介面類
  • InvocactionHandler介面的一個實現

proxy

動態代理可以將所有呼叫重定向到呼叫處理器,因此通常上會向呼叫處理器的構造器傳遞一個"實際"物件的引用,從而使得處理器在執行任務時,可以請求轉發。

利用CGLIB實現動態代理

cglib是一種基於ASM的位元組碼生成庫,用於生成和轉換Java位元組碼.

而ASM是一個輕量但高效能的位元組碼操作框架。cglib是基於ASM的上層應用,對於代理沒有實現介面的類,cglib非常實用。

cglib

CGLIB動態代理的簡單例子

本質上說,對於需要被代理的類,它只是動態生成一個子類以覆蓋非final的方法,同時繫結鉤子回撥自定義的攔截器。

新增CGLIB依賴

 <dependency>
      <groupId>cglib</groupId>
      <artifactId>cglib</artifactId>
      <version>3.2.4</version>
 /dependency>
複製程式碼
package com.pjmike.proxy;

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

import java.lang.reflect.Method;

/**
 * CGLIB動態代理實現
 *
 * @author pjmike
 * @create 2018-08-06 16:55
 */
public class CglibProxy {
    public static void main(String[] args) {
        //Enhancer是CGLIB的核心工具類,是一個位元組碼增強器,它可以方便的對類進行擴充套件
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(PersonService.class);
        //設定回撥所需的攔截器
        enhancer.setCallback(new MyMethodInterceptor());
        //通過enhancer.create()方法獲取代理物件
        //對代理物件所有非final的方法呼叫都會轉發給MethodInterceptor.intercept方法,
        //作用跟JDK動態代理的InvocationHandler類似
        PersonService personService = (PersonService) enhancer.create();
        System.out.println(personService.sayHello("pjmike"));
    }
}

class PersonService {
    public String sayHello(String name) {
        return "Hello, " + name;
    }
}

class MyMethodInterceptor implements MethodInterceptor {

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        return methodProxy.invokeSuper(obj, args);
    }
}
複製程式碼

至於上面提到的javassist也是需要直接操作位元組碼,跟ASM類似,所以這兩者使用門檻比較高,一般用於框架的底層實現。比如hibernate底層使用了javassist和cglib.

javassist

javassist是一個執行時編譯庫,可以動態的生成或修改類的位元組碼。它有兩種實現動態代理的方案: javassist提供的動態代理介面和javassist位元組碼。

因為javassist提供動態代理介面比較慢,所以這裡主要分析javassist位元組碼,先不考慮其動態代理介面。至於這幾種代理方案的效能比較問題,參考動態代理方案效能對比

javassist主要由CtClass,CtMethod,CtField幾個類組成,與JDK反射中的Class,Method,Field相似。

簡單使用

新增依賴

<dependency>
      <groupId>org.javassist</groupId>
      <artifactId>javassist</artifactId>
      <version>3.21.0-GA</version>
</dependency>
複製程式碼

程式碼

package com.pjmike.proxy;

import javassist.*;

/**
 * javassist位元組碼
 *
 * @author pjmike
 * @create 2018-08-07 0:07
 */
public class JavassistByteCode {
    public static void main(String[] args) throws IllegalAccessException, CannotCompileException, InstantiationException, NotFoundException {
        ByteCodeAPI byteCodeApi = createJavassistBycodeDynamicProxy();
        System.out.println(byteCodeApi.sayHello());
    }
    public static ByteCodeAPI createJavassistBycodeDynamicProxy() throws CannotCompileException, IllegalAccessException, InstantiationException, NotFoundException {
        //獲取執行時類的上下文
        ClassPool pool = ClassPool.getDefault();
        //動態建立類
        CtClass cc = pool.makeClass(ByteCodeAPI.class.getName()+"demo");
        cc.addInterface(pool.get(ByteCodeAPI.class.getName()));
        //建立屬性
        CtField field = CtField.make("private String a;", cc);
        cc.addField(field);
        //建立方法
        CtMethod method = CtMethod.make("public String sayHello() {return \"hello\";}", cc);
        cc.addMethod(method);
        //新增構造器
        cc.addConstructor(CtNewConstructor.defaultConstructor(cc));
        Class<?> pc = cc.toClass();
        ByteCodeAPI byteCodeApi = (ByteCodeAPI) pc.newInstance();
        return byteCodeApi;
    }
}
interface ByteCodeAPI {
    public String sayHello();
}

//結果:輸出 hello
複製程式碼

動態代理的實際應用

動態代理實際上有很多應用,比如spring aop的實現,rpc框架的實現,一些第三方工具庫的內部使用等等。這裡簡單介紹動態代理在spring aop和RPC框架中的應用

應用一: Spring AOP的動態代理實現

Spring AOP的動態代理實現主要有兩種方式,JDK動態代理和CGLIB位元組碼生成。

預設情況下,如果Spring AOP發現目標物件後實現了相應的interface,則採用JDK動態代理機制為其生成代理物件。如果沒有發現介面,則採用CGLIB的方式為目標物件生成動態的代理物件例項

應用二: RPC框架中的應用

RPC即遠端過程呼叫,它的實現中使用到了動態代理,關於RPC的具體原理參照你應該知道的RPC原理等文章

rpc

實際上RPC框架要解決的一個問題就是: 如何呼叫他人的遠端服務?像呼叫本地服務一樣呼叫遠端服務。

如何封裝資料,通過網路傳輸給遠端服務,使遠端介面透明,這就需要動態代理的幫助了。所以透明化遠端服務呼叫就是要利用動態代理,在代理層對資料進行封裝,網路傳輸等操作。

小結

實際開發中,我們很多時候都是利用現成的框架和開源庫,其中就包含動態代理的應用。只有真正瞭解了動態代理的知識,才能更好地理解其在框架中的設計,也有利於更好的去使用框架。

參考資料

相關文章