動態代理的原理及其應用

動態代理的介紹

動態代理是一種在運行時動態地建立代理對象,動態地處理代理方法調用的機制。html

實際上它是一種代理機制。代理能夠看作是對調用目標的一個封裝,直接經過代理來實現對目標代碼的調用java

與靜態代理的比較

靜態代理

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

  • ISubject,該接口是被訪問者或者被訪問的對象
  • SubjectImpl,被訪問者的具體實現類
  • SubjectProxy,被訪問者或被訪問資源的代理實現類
  • Client:表明訪問者的抽象角色,client將會訪問Isubject類型的對象或者資源,經過代理類進行訪問。
  • 這裏的SubjectImpl和SubjectProxy都實現了ISubject的接口,SubjectProxy是將請求轉發給SubjectImpl,其內部有SubjectImpl的對象,而且能夠添加一些限制

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

動態代理是運行時自動生成代理對象,一個缺點是生成代理對象和調用代理方法須要耗費時間spring

動態代理的實現方式

動態代理主要有兩種實現方式,一種是JDK動態代理,一種是CGLIB字節碼機制,固然還有Javassist或ASM庫,這兩個在CGLIB那塊一併介紹編程

JDK動態代理

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

反射機制

反射就是經過Class類和java.lang.reflect類庫在運行時獲取某個類的信息。好比經過java.lang.reflect類庫中Field,Method以及Constructor類就能夠獲取類的相關信息數組

JDK動態代理的實現

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

/**
 * 動態代理的實現
 *
 * @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框架要解決的一個問題就是: 如何調用他人的遠程服務?像調用本地服務同樣調用遠程服務。

如何封裝數據,經過網絡傳輸給遠程服務,使遠程接口透明,這就須要動態代理的幫助了。因此透明化遠程服務調用就是要利用動態代理,在代理層對數據進行封裝,網絡傳輸等操做。

小結

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

參考資料

相關文章
相關標籤/搜索