Java代理模式,一次複習完4種動態代理實現方式

代理模式也是一種非常常見的設計模式。了解Spring框架的都知道,Spring AOP 使用的就是動態代理模式。今天就來系統的重溫一遍代理模式。

在現實生活中代理是隨處可見的,當事人因某些隱私不方便出面,或者當事人不具備某些相關的專業技能,而需要一個職業人員來完成一些專業的操作,
也可能由於當事人沒有時間處理事務,而聘用代理人出面。而在軟體設計中,使用代理模式的地方也很多,由於安全原因,屏蔽客戶端直接訪問真實對象,
或者為了提升系統性能,使用代理模式實現延遲載入,還有就是AOP,對委託類的功能進行增強等。

一、代理模式的結構

代理模式的主要參與者有4個,如下圖所示:

角色 作用
Subject 主題介面,定義了代理類和委託類的公共對外方法,也是代理類代理委託類的方法
RealSubject 委託類,真實主題,真正實現業務邏輯的類
Proxy 代理類,代理和封裝委託類
Client 客戶端,使用代理類和主題介面完成業務邏輯

二、代理模式的實現

代理模式一般分為靜態代理和動態代理兩種:

  • 靜態代理,顧名思義,就是提前創建好代理類文件並在程式運行前已經編譯成位元組碼。
  • 動態代理,是指在運行時動態生成代理類,即代理類的位元組碼將在運行時生成並載入到ClassLoader中。

了解了兩種代理模式大概區別後,接下來就以一個簡訊發送功能增強的示例來詳細闡述兩種代理的實現方式。

1、靜態代理實現

第一步,定義主題介面,該介面只有一個send方法:

public interface ISender {

    public boolean send();
}

第二步,定義主題真正實現類:

public class SmsSender implements ISender {
    public boolean send() {
        System.out.println("sending msg");
        return true;
    }
}

第三步,創建代理類,封裝實現類:

public class ProxySender implements ISender {

    private ISender sender;

    public ProxySender(ISender sender){
        this.sender = sender;
    }

    public boolean send() {
        System.out.println("處理前");
        boolean result = sender.send();
        System.out.println("處理後");
        return result;
    }
}

第四步,客戶端調用:

@Test
public void testStaticProxy(){
    ISender sender = new ProxySender(new SmsSender());
    boolean result = sender.send();
    System.out.println("輸出結果:" + result);
}

以上就實現了一個簡單的靜態代理,很明顯,靜態代理需要為每個真實主題定義一個形式上完全一樣的封裝類,
如果真實主題方法有所修改,那代理類也需要跟著修改,不利於系統的維護。

2、動態代理實現

與靜態代理相比,動態代理有更多優勢,動態代理不僅不需要定義代理類,甚至可以在運行時指定代理類的執行邏輯,
從而大大提升系統的靈活性。

目前動態代理類的生成方法有很多,有JDK自帶的動態代理、CGLIB、Javassist和ASM庫等。

  • JDK動態代理:內置在JDK中,不需要引入第三方jar,使用簡單,但功能比較弱。
  • CGLIB/Javassist:這兩個都是高級的位元組碼生成庫,總體性能比JDK動態代理好,且功能強大。
  • ASM:低級位元組碼生成工具,近乎使用bytecode編碼,對開發人員要求最高。當然性能也是最好(相比前幾種也不是很大的提升,這裡不做具體介紹)。

以下實例依然以SmsSenderISender作為被代理對象和介面進行試驗。

1) JDK動態代理

JDK的動態代理需要實現一個處理方法調用的Handler,用於實現代理方法的內部邏輯,實現InvocationHandler介面。

public class JdkProxyHandler implements InvocationHandler {

    private Object target;

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

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("處理前");
        Object result = method.invoke(target,args);
        System.out.println("處理後");
        return result;
    }
}

客戶端調用:

@Test
public void testJdkProxy(){
    ISender sender = (ISender) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
            new Class[]{ISender.class},
            new JdkProxyHandler(new SmsSender()));
    boolean result = sender.send();
    System.out.println("代理對象:" + sender.getClass().getName());
    System.out.println("輸出結果:" + result);
}

輸出結果:

處理前
sending msg
處理後
代理對象:com.sun.proxy.$Proxy4
輸出結果:true

這樣實現一個簡單的AOP就完成了,我們看到代理類的類型是com.sun.proxy.$Proxy4。那JDK是如何創建代理類?
首先從Proxy.newProxyInstance入手,來研究JDK是如何生成代理類:

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)

該方法有3個參數:

  • loader:用哪個類載入器去載入代理對象,生成目標對象的代理需要確保其類載入器相同,所以需要將目標對象的類載入器作為參數傳遞。
  • interfaces:代理類需實現的介面列表,JDK動態代理技術需要代理類和目標對象都繼承自同一介面,所以需要將目標對象的介面作為參數傳遞。
  • h:調用處理器,調用實現了InvocationHandler類的一個回調方法,對目標對象的增強邏輯在這個實現類中。

具體程式碼如下:

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h) throws IllegalArgumentException {
    //1.檢查
    Objects.requireNonNull(h);

    final Class<?>[] intfs = interfaces.clone();
    final SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
    }

    /*
     * Look up or generate the designated proxy class.
     */
    //獲取代理類類型
    Class<?> cl = getProxyClass0(loader, intfs);

    /*
     * Invoke its constructor with the designated invocation handler.
     */
    try {
        if (sm != null) {
            checkNewProxyPermission(Reflection.getCallerClass(), cl);
        }

        //通過反射創建代理對象
        final Constructor<?> cons = cl.getConstructor(constructorParams);
        final InvocationHandler ih = h;
        if (!Modifier.isPublic(cl.getModifiers())) {
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    cons.setAccessible(true);
                    return null;
                }
            });
        }
        return cons.newInstance(new Object[]{h});
    } catch (IllegalAccessException|InstantiationException e) {
        throw new InternalError(e.toString(), e);
    } catch (InvocationTargetException e) {
        Throwable t = e.getCause();
        if (t instanceof RuntimeException) {
            throw (RuntimeException) t;
        } else {
            throw new InternalError(t.toString(), t);
        }
    } catch (NoSuchMethodException e) {
        throw new InternalError(e.toString(), e);
    }
}

總結:具體程式碼細節就不在這裡深究,但可以明顯的看出,JDK的動態代理底層是通過Java反射機制實現的,並且需要目標對象繼承自一個介面才能生成它的代理類

2) CGLIB(Code Generation Library)動態代理

使用CGLIB動態代理前需要引入依賴:

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

和JDK代理不同,CGLib動態代理技術不需要目標對象實現自一個介面,只需要實現一個處理代理邏輯的切入類,並實現MethodInterceptor介面。

定義真實主題實現類:

public class BdSender {

    public boolean send() {
        System.out.println("sending msg");
        return true;
    }
}

代理類邏輯處理類:

public class CglibProxyInterceptor implements MethodInterceptor {

    private Enhancer enhancer = new Enhancer();

    /**
     * 獲取代理類
     * @param clazz
     * @return
     */
    public Object getProxy(Class clazz) {
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        return enhancer.create();
    }

    public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("處理前");
        Object result = methodProxy.invokeSuper(object,args);
        System.out.println("處理後");
        return result;
    }
}

客戶端調用:

@Test
public void testCglibProxy(){
    BdSender sender = (BdSender) new CglibProxyInterceptor().getProxy(BdSender.class);
    boolean result = sender.send();
    System.out.println("代理對象:" + sender.getClass().getName());
    System.out.println("輸出結果:" + result);
}

輸出結果:

處理前
sending msg
處理後
代理對象:org.yd.proxy.BdSender$$EnhancerByCGLIB$$d65f9e34
輸出結果:true

總結CgLib的特點:

  • 使用CGLib實現動態代理,完全不受代理類必須實現介面的限制
  • CGLib底層採用ASM位元組碼生成框架,使用位元組碼技術生成代理類,比使用Java反射效率要高
  • CGLib不能對聲明為final的方法進行代理,因為CGLib原理是動態生成被代理類的子類

3) Javassist動態代理

Javassist是一個開源的分析、編輯和創建Java位元組碼的類庫,可以直接編輯和生成Java生成的位元組碼。
相對於bcel, asm等這些工具,開發者不需要了解虛擬機指令,就能動態改變類的結構,或者動態生成類。

使用avassist動態代理前需要引入依賴:

<dependency>
    <groupId>org.javassist</groupId>
    <artifactId>javassist</artifactId>
    <version>3.27.0-GA</version>
</dependency>

使用Javassist生成動態代理可以有以下兩種方式:

  • 代理工廠創建:需要實現MethodHandler用於代理邏輯處理,實現與CGLib非常類似
  • 動態程式碼創建:可通過Java程式碼生成位元組碼,這種方式創建的動態代理非常靈活,甚至可以在運行時生成業務邏輯
代理工廠創建 — 代理邏輯處理類
public class JavassistProxyHandler implements MethodHandler {

    private ProxyFactory proxyFactory = new ProxyFactory();

    /**
     * 獲取代理對象
     * @param clazz 被代理類
     * @return
     * @throws Exception
     */
    public Object getProxy(Class clazz) throws Exception {
        proxyFactory.setSuperclass(clazz);
        Class<?> factoryClass = proxyFactory.createClass();
        Object proxy = factoryClass.newInstance();
        ((ProxyObject)proxy).setHandler(this);
        return proxy;
    }

    public Object invoke(Object object, Method method, Method method1, Object[] args) throws Throwable {
        System.out.println("處理前");
        Object result = method1.invoke(object,args);
        System.out.println("處理後");
        return result;
    }
}

客戶端調用:

@Test
public void testJavassistProxy() throws Exception {
    BdSender sender = (BdSender) new JavassistProxyHandler().getProxy(BdSender.class);
    boolean result = sender.send();
    System.out.println("代理對象:" + sender.getClass().getName());
    System.out.println("輸出結果:" + result);
}

輸出結果

處理前
sending msg
處理後
代理對象:org.yd.proxy.BdSender_$$_jvstbce_0
輸出結果:true
動態程式碼創建 — 代理邏輯處理類:
public static Object getProxy(Class clazz) throws Exception {
    ClassPool mPool = ClassPool.getDefault();
    CtClass c0 = mPool.get(clazz.getName());
    //定義代理類名稱
    CtClass mCtc = mPool.makeClass(clazz.getName() + "$$BytecodeProxy");
    //添加父類繼承
    mCtc.setSuperclass(c0);
    //添加類的欄位資訊
    CtField field = new CtField(c0, "real", mCtc);
    field.setModifiers(AccessFlag.PRIVATE);
    mCtc.addField(field);
    //添加構造函數
    CtConstructor constructor = new CtConstructor(new CtClass[]{c0},mCtc);
    constructor.setBody("{$0.real = $1;}"); // $0代表this, $1代表構造函數的第1個參數
    mCtc.addConstructor(constructor);
    //添加方法
    CtMethod ctMethod = mCtc.getSuperclass().getDeclaredMethod("send");
    CtMethod newMethod = new CtMethod(ctMethod.getReturnType(), ctMethod.getName(),ctMethod.getParameterTypes(), mCtc);
    newMethod.setBody("{" +
            "System.out.println(\"處理前\");" +
            "boolean result = $0.real.send();" +
            "System.out.println(\"處理後\");" +
            "return result;}");
    mCtc.addMethod(newMethod);

    //生成動態類
    return mCtc.toClass().getConstructor(clazz).newInstance(clazz.newInstance());
}

客戶端調用:

@Test
public void testJavassisBytecodetProxy() throws Exception {
    BdSender sender = (BdSender) JavassistDynamicCodeProxy.getProxy(BdSender.class);
    boolean result = sender.send();
    System.out.println("代理對象:" + sender.getClass().getName());
    System.out.println("輸出結果:" + result);
}

輸出結果:

處理前
sending msg
處理後
代理對象:org.yd.proxy.BdSender$$BytecodeProxy
輸出結果:true

Javassist被用於struts2和hibernate中,都用來做動態位元組碼修改使用。一般開發中不會用到,但在封裝框架時比較有用。
具體語法可參考:Javassist官方地址傳送門

以上介紹了靜態代理和動態代理創建的幾種方法與優缺點介紹,希望可以幫到大家。