代理模式 --- 靜態代理與動態代理;動態代理的實現原理;手寫實現自己的動態代理;比較jdk與cglib;

這篇文章,主要解決以下幾個問題:

  1. 分析靜態代理存在的問題。
  2. 分析jdk動態代理執行過程和實現原理。
  3. 動手實現自己的動態代理。
  4. 分析CGlib的動態代理實現原理。
  5. 比較CGlib動態代理和jdk動態代理。

1 靜態代理及存在的問題

1.1 靜態代理

在實際的開發中,我們可能會遇到這種問題,如:現在有一個訂單的服務接口,以及其實現類如下:

/**
 * 訂單服務
 */
public interface OrderService{
    /**
     * 創建訂單
     * @throws InterruptedException
     */
    void createOrder() throws InterruptedException;
}
/**
 * 訂單服務實現
 */
public class OrderServiceImpl implements OrderService {
    @Override
    public void createOrder() {
        System.out.println("創建訂單");
    }
}

我們現在有新的需求,需要知道這個服務中createOrder方法的執行時間,我們會怎麼做呢,最簡單最容易的想法就是去修改createOrder方法,或修改調用createOrder方法的代碼,在調用createOrder前後增加一些邏輯。然而這樣需要修改源代碼,不符合開閉原則。

我們可以通過靜態代理的方式去實現這個需求,具體做法如下:

  1. 創建一個代理類OrderServiceProxy,實現OrderService。
  2. OrderServiceProxy中持有被代理對象的引用OrderService,創建OrderServiceProxy代理對象的時候,需要告訴OrderServiceProxy被代理的對象是誰。
  3. 代理類OrderServiceProxy中,重寫OrderService接口方法。接口中調用具體的被代理對象的相應方法,並且可以在被代理對象方法前後,異常捕獲catch,finally等位置增加邏輯。這裏只在方法調用的前後增加邏輯完成打印方法執行時間的功能。

前三步實現如下:

public class OrderServiceProxy implements OrderService {
    /**
     * 代理對象持有被代理對象的引用
     */
    private OrderService orderService;
    private Long startTime = 0L;

    /**
     * 創建代理對象的時候,傳入被代理的對象
     * @param orderService
     */
    public OrderServiceProxy(OrderService orderService){
        this.orderService = orderService;
    }

    @Override
    public void createOrder() {
        beforeTime("createOrder");
        //調用被代理對象的方法
        orderService.createOrder();
        afterTime("createOrder");
    }
    /**
     * 方法調用之前執行
     * @param methodName
     */
    public void beforeTime(String methodName){
        startTime = System.currentTimeMillis();
        System.out.println(methodName + "開始執行時間:" + startTime);
    }
    /**
     * 方法調用之後執行
     * @param methodName
     */
    public void afterTime(String methodName){
        System.out.println(methodName + "執行總共用時:" + (System.currentTimeMillis() - startTime));
    }
}
  1. 最後,具體調用地方創建OrderService對象的時候,創建的是一個代理類對象OrderServiceProxy,並且告訴OrderServiceProxy具體代理的對象是誰(這裏是OrderService實現類OrderServiceImpl)
public class StaticProxyTest {
    public static void main(String[] args) throws Exception{
        OrderService orderService = new OrderServiceProxy(new OrderServiceImpl());
        orderService.createOrder();
    }
}

這樣就完成了,程序執行createOrder的時候,調用過程如下:

  1. mian方法中調用的是代理類OrderServiceProxy的createOrder方法
  2. 在OrderServiceProxy的createOrder中,先調用增加的邏輯before方法
  3. 然後調用具體的被代理類的createOrder方法
  4. 最後再調用增加的邏輯after方法。

這樣就起到了對被代理類OrderServiceImpl功能的增強。

執行結果如下:

執行結果

1.2 靜態代理存在問題

(1)如果OrderService中增加新的功能,如增加queryOrder方法,此時,代理類也需要手動實現新增加的方法。如下:

/**
 * 訂單服務
 */
public interface OrderService{
    /**
     * 創建訂單
     * @throws InterruptedException
     */
    void createOrder();

    /**
     * 查詢訂單
     */
    void queryOrder();
}
public class OrderServiceProxy implements OrderService {
    /**
     * 代理對象持有被代理對象的引用
     */
    private OrderService orderService;
    private Long startTime = 0L;

    /**
     * 創建代理對象的時候,傳入被代理的對象
     * @param orderService
     */
    public OrderServiceProxy(OrderService orderService){
        this.orderService = orderService;
    }

    @Override
    public void createOrder() {
        beforeTime("createOrder");
        //調用被代理對象的方法
        orderService.createOrder();
        afterTime("createOrder");
    }

    @Override
    public void queryOrder() {
        orderService.createOrder();
    }

    /**
     * 方法調用之前執行
     * @param methodName
     */
    public void beforeTime(String methodName){
        startTime = System.currentTimeMillis();
        System.out.println(methodName + "開始執行時間:" + startTime);
    }
    /**
     * 方法調用之後執行
     * @param methodName
     */
    public void afterTime(String methodName){
        System.out.println(methodName + "執行總共用時" + (System.currentTimeMillis() - startTime));
    }
}

(2)現在不僅僅有訂單服務,還有支付服務,同樣支付服務業需要知道支付過程中方法的性能,需要打印時間。此時我們需要爲支付服務手動增加一個代理類,在這個代理類中手動實現所有的支付接口,並手動爲每個方法增加before和after方法打印方法的執行時間。如下支付接口:

/**
 * 支付服務
 */
public interface PaymentService {
    /**
     * 支付
     */
    void pay();
}
/**
 * 支付服務實現
 */
public class PaymentServiceImpl implements PaymentService {
    @Override
    public void pay() {
        System.out.println("支付");
    }
}

顯然,OrderServiceProxy是不能夠代理支付的服務,因爲OrderServiceProxy中持有的是訂單服務OrderService的引用。

所以,需要爲 PaymentService創建一個自己的代理類 PaymentServiceProxy,實現 PaymentService接口,並持有 PaymentService接口引用,如下:

public class PaymentServiceProxy implements PaymentService {
    private PaymentService paymentService;
    private Long startTime = 0L;
    public PaymentServiceProxy(PaymentService paymentService){
        this.paymentService = paymentService;
    }
    @Override
    public void pay() {
        beforeTime("pay");
        paymentService.pay();
        afterTime("pay");
    }

    /**
     * 方法調用之前執行
     * @param methodName
     */
    public void beforeTime(String methodName){
        startTime = System.currentTimeMillis();
        System.out.println(methodName + "開始執行時間:" + startTime);
    }
    /**
     * 方法調用之後執行
     * @param methodName
     */
    public void afterTime(String methodName){
        System.out.println(methodName + "執行總共用時" + (System.currentTimeMillis() - startTime));
    }
}

這樣,每次需要對一個服務進行功能增強,都需要手動爲這個服務增加一個屬於自己的代理類,這樣會導致類的數量增多,增加系統的複雜程度,不易於維護。

2 Jdk動態代理

前面我們知道,靜態代理是存在兩個問題的:

  1. 新增方法,都需要在代理類中手動實現方法。
  2. 新增服務,需要爲每個服務手動增加一個實現類,即使代理類都是爲了完成同一同能(如上面都是爲了打印方法的執行時間)。

動態代理能夠很好的解決這兩個問題,目前動態代理有兩種方式,JDK動態代理和CGLib動態代理。我們先看看Jdk動態代理是如何使用的:

2.1 jdk動態代理使用

首先,需要實現自己的InvocationHandler,這裏的InvocationHandler是對被代理對象和增強功能的封裝,jdk動態創建的代理類對象會持有InvocationHandler的引用,具體實現如下:

public class JdkInvocationHandler implements InvocationHandler {
    /**
     * 被代理的對象
     */
    private Object object;
    public JdkInvocationHandler(Object object){
        this.object = object;
    }
    /**
     * 這個方法由代理類調用,此時代理類還沒有被創建,是在程序執行的時候動態創建的
     * @param proxy 代理類引用,代理類調用此方法的時候傳遞自身引用this
     * @param method 要執行被代理類的哪個方法,這個是代理類獲取被代理類接口中的Method作爲參數傳來的。
     *               jdk動態創建的代理類和被代理類會實現相同的接口,所以這個Method對象很容易獲得
     * @param args 要執行被代理類的方法的參數列表
     * @return 方法返回值
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("調用前做一些事情,before");
       Object result = method.invoke(object, args);
        System.out.println("調用後做一些事情,after");
        return result;
    }
    
}

InvocationHandler中有一個方法invoke,invoke供代理類調用,有三個參數:

參數1:代理類引用,代理類調用此方法的時候傳遞自身引用this。

參數2:要執行被代理類的哪個方法,invoke中通過method.invoke(object, args)調用被代理對象object的相應方法。

參數3:要執行方法的參數列表。

動態創建代理對象,以及調用的過程如下:

public class JdkProxyTest {
    public static void main(String[] args) {
        //創建代理對象
        ClassLoader classLoader = JdkProxyTest.class.getClassLoader();
        OrderService orderServiceProxy = (OrderService) Proxy.newProxyInstance(
                OrderService.class.getClassLoader(),
                OrderServiceImpl.class.getInterfaces(),
                new JdkInvocationHandler(new OrderServiceImpl()));
        orderServiceProxy.createOrder();
    }
}

通過調用Proxy.newProxyInstance()動態創建代理類對象,newProxyInstance有三個參數:

參數1:類加載器,運行的時候動態創建的代理類會通過這個類加載器進行加載。

參數2:被代理類實現的接口列表,將來被代理類也會實現這些接口。

參數3:前面實現的InvocationHandler對象,將來被創建的代理類會持有這個對象的引用,用於調用InvocationHandler中的invoke方法。

2.2 jdk動態代理原理分析

到這裏,Jdk動態代理就寫好了,jdk動態代理看似神祕,其實原理很簡單:

  1. 通過Proxy.newProxyInstance()創建動態創建被代理對象,被創建的代理對象是什麼樣子的:一是實現了被代理對象所實現的全部接口,並重寫了相應的方法(所以需要第二個參數); 二是持有InvocationHandler對象引用(第三個參數)。
  2. 具體調用的時候,首先調用代理類的某個方法。
  3. 代理類的所有的方法中,都調用的是InvocationHandler的invoke方法,告知InvocationHandler的invoke我現在調用的方法是誰,並把方法參數傳遞給InvocationHandler的invoke。
  4. InvocationHandler的invoke中執行具體的被代理對象的相應方法。並在執行具體被代理對象的方法前對其功能進行加強。

具體調用過程如下:

在這裏插入圖片描述

爲了能更清楚的看清楚jdk動態代理的真實面目,下面,我們將jdk動態生成的代理類class字節碼寫到文件中,通過反編譯工具進行反編譯,看看動態生成的代理類類是什麼樣的。

輸出動態代理類class代碼如下:

public class DynamicProxyTest {
    public static void main(String[] args) throws Exception{
        byte[] bytes = ProxyGenerator.generateProxyClass("Proxy0", new Class[]{OrderService1.class});
        FileOutputStream os = new FileOutputStream("d:\\install\\jad\\Proxy.class");
        os.write(bytes);
        os.close();
    }
}

對Proxy.class文件進行反編譯結果如下:

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

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

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

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

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

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

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (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");
            m3 = Class.forName("blog.designpatterns.proxy.OrderService1").getMethod("createOrder");
            m4 = Class.forName("blog.designpatterns.proxy.OrderService1").getMethod("createOrder", Integer.TYPE);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

很清楚就能看到,動態生成的代理類:

  1. 實現了被代理類所實現的接口,並重寫了其方法。
  2. 持有InvocationHandler引用。
  3. 調用動態代理類的方法,方法中都是調用了InvocationHandler的invoke方法,告訴InvocationHandler現在要調用的是哪個方法,並且傳遞自身引用和參數列表。

動態代理能夠很好的解決靜態代理的兩個問題,原因是:

  1. InvocationHandler中持有引用對象是Object,可以代理所有的服務。
  2. 代理類是通過Proxy.newProxyInstance()動態創建的,不需要手動去爲每個服務都手動實現一個代理類。
  3. 代理類動態創建,接口中的方法增多,會自動實現所有的方法,不需要手動實現。

jdk動態代理還有兩個細節:

  1. 代理類重寫的方法被final修飾,說明限制了代理類不能再次被繼承(後面講到的Cglib動態代理不能代理final修飾的方法,所以也限制了被jdk動態代理類方法不能再次被代理)。
  2. 除了代理我們定義的方法,還代理了equals, toString,hashCode。

3 實現自己的動態代理

這裏是仿照jdk的動態代理,實現自己的動態代理,旨在更進一步的瞭解jdk動態代理的原理。

要仿照jdk動態代理實現自己的動態代理,主要分爲兩步:

  1. 定義自己的InvocationHandler接口。
  2. 動態創建代理類對象。

3.1 定義自己的InvocationHandler接口

和jdk的InvocationHandler一樣定義,具體如下:

public interface MyInvocationHandler {
    Object invoke(Object proxy, Method method, Object[] args);
}

3.2 動態創建代理類對象

動態創建代理類對象可以拆分爲以下幾個步驟:

  1. 代碼構建Java程序,可以是文本文件,可以只是字符串,這個組裝爲一個字符串。
  2. 編譯java程序,生成Class byte數組。
  3. 通過構造器加載Class,這裏會定義自己的類加載器。
  4. 通過加載的Class,創建代理對象。

四個步驟定義如下:

public class MyProxy {
    public static Object newProxyInstance(MyClassLoader classLoader, Class[] interfaces, MyInvocationHandler invocationHandler) throws Exception {
        //1、代碼構建Java程序,可以是文本文件,可以只是字符串,這個組裝爲一個字符串
        String proxyJavaSource =  buildProxyJavaSource(interfaces);
        //2、編譯java程序,生成Class byte數組
        byte[] classBytes = compailerJavaSource(proxyJavaSource);
        //3、通過構造器加載Class
        Class proxyClass = loadClass(classLoader, classBytes);
        //4、創建代理對象
        Object proxy = createdProxyInstance(proxyClass, invocationHandler);
        return proxy;
    }
}

下面就具體實現這四個步驟

3.2.1 構建java程序

構建java代碼,其實就是通過程序去寫程序,過程很簡單,只需要注意一些實現細節即可。這裏寫法很撈,具體實現如下:

/**
     * 代碼構建Java程序,可以是文本文件,可以只是字符串,這個組裝爲一個字符串
     * @param interfaces 被代理類實現的接口
     * @return java源程序字符串
     */
    private static String buildProxyJavaSource(Class[] interfaces) {
        StringBuilder builder = new StringBuilder();
        builder.append("package blog.designpatterns.proxy.dynamicproxy.mydynamicproxy;\n");

        //包導入
        builder.append("import blog.designpatterns.proxy.dynamicproxy.mydynamicproxy.MyInvocationHandler;\n");
        builder.append("import java.lang.reflect.Method;\n");
        for (Class ainterface : interfaces) {
            builder.append("import " + ainterface.getName() + ";\n");
        }

        //實現接口
        builder.append("public class Proxy0 implements ");
        for (Class ainterface : interfaces) {
            builder.append(" " + ainterface.getSimpleName() + ",");
        }
        builder.deleteCharAt(builder.length() - 1);
        builder.append("{\n");


        //方法變量
        int index = 0;
        for (int i = 0; i < interfaces.length; i++) {
            Class anInterface = interfaces[i];
            Method[] declaredMethods = anInterface.getDeclaredMethods();
            for (int j = 0; j < declaredMethods.length; j++) {
                builder.append("private static Method m" + index + ";\n");
                index ++;
            }
        }

        //靜態代碼塊
        index = 0;
        builder.append("static {\n");
        builder.append("try{\n");
        for (int i = 0; i < interfaces.length; i++) {
            Class anInterface = interfaces[i];
            Method[] declaredMethods = anInterface.getDeclaredMethods();
            for (int j = 0; j < declaredMethods.length; j++) {
                Method method = declaredMethods[j];
                builder.append("m" + index + " = Class.forName(\""+anInterface.getName()+"\").getMethod(\""+method.getName()+"\",");
                for (Class<?> parameterType : method.getParameterTypes()) {
                    builder.append(parameterType.getName() + ".class,");
                }
                builder.deleteCharAt(builder.length() - 1);
                builder.append(");\n");
                index ++;
            }
        }
        builder.append("} catch (Exception e){ \n");
        builder.append("throw new RuntimeException(e);\n");
        builder.append("}\n");
        builder.append("}\n");

        //handler和構造函數
        builder.append("private MyInvocationHandler myInvocationHandler;\n");
        builder.append("public Proxy0(MyInvocationHandler myInvocationHandler) throws Exception {\n");
        builder.append("this.myInvocationHandler = myInvocationHandler;\n");
        builder.append(" }\n");

        //重寫方法
        index = 0;
        for (Class ainterface : interfaces) {
            Method[] declaredMethods = ainterface.getDeclaredMethods();
            for (Method method : declaredMethods){
                builder.append("@Override\n");
                builder.append("public ");
                builder.append(method.getReturnType().getName() + " ");
                builder.append(method.getName() + "( ");
                Class<?>[] parameterTypes = method.getParameterTypes();
                for (int i = 0; i < parameterTypes.length; i++) {
                    builder.append(parameterTypes[i].getName() + " var" + i + ",");
                }
                if (parameterTypes.length > 0){
                    builder.deleteCharAt(builder.length() - 1);
                }
                builder.append("){\n");
                builder.append("try{\n");
                //調用handler的invoke方法

                if (method.getReturnType().getName().equals("void")){
                    builder.append("myInvocationHandler.invoke(this, m" + index+ ",");
                }else {
                    builder.append("Object result = myInvocationHandler.invoke(this, m" + index+ ",");
                }
                if (parameterTypes.length == 0){
                    builder.append("null");
                }else {
                    builder.append("new Object[]{");
                    for (int i = 0; i < parameterTypes.length; i++) {
                        builder.append(" var" + i + ",");
                    }
                    builder.deleteCharAt(builder.length() - 1);
                    builder.append("}");
                }
                builder.append(");\n");
                if (!method.getReturnType().getName().equals("void")){
                    builder.append("return ("+method.getReturnType().getName()+")result;\n");
                }

                builder.append("} catch (Exception e){ \n");
                builder.append("throw new RuntimeException(e);\n");
                builder.append("}\n");
                builder.append("}\n");
                index ++;
            }
        }
        builder.append("}\n");
        return builder.toString();
    }

定義測試OrderService接口如下:

/**
 * 訂單服務
 */
public interface OrderService {
    /**
     * 創建訂單
     * @throws InterruptedException
     */
    void createOrder();


    /**
     * 查詢訂單
     */
    String createOrder(int a);
}

執行上面的方法構建代理類java源程序結果如下:

package blog.designpatterns.proxy.dynamicproxy.mydynamicproxy;
import blog.designpatterns.proxy.dynamicproxy.mydynamicproxy.MyInvocationHandler;
import java.lang.reflect.Method;
import blog.designpatterns.proxy.dynamicproxy.mydynamicproxy.OrderService;
public class Proxy0 implements  OrderService{
    private static Method m0;
    private static Method m1;
    static {
        try{
            m0 = Class.forName("blog.designpatterns.proxy.dynamicproxy.mydynamicproxy.OrderService").getMethod("createOrder");
            m1 = Class.forName("blog.designpatterns.proxy.dynamicproxy.mydynamicproxy.OrderService").getMethod("createOrder",int.class);
        } catch (Exception e){
            throw new RuntimeException(e);
        }
    }
    private MyInvocationHandler myInvocationHandler;
    public Proxy0(MyInvocationHandler myInvocationHandler) throws Exception {
        this.myInvocationHandler = myInvocationHandler;
    }
    @Override
    public void createOrder( ){
        try{
            myInvocationHandler.invoke(this, m0,null);
        } catch (Exception e){
            throw new RuntimeException(e);
        }
    }
    @Override
    public java.lang.String createOrder( int var0){
        try{
            Object result = myInvocationHandler.invoke(this, m1,new Object[]{ var0});
            return (java.lang.String)result;
        } catch (Exception e){
            throw new RuntimeException(e);
        }
    }
}

3.2.2 編譯java程序

編譯上面寫好的java程序字符串。編譯使用jdk提供的tools進行編譯,細節不講,直接上代碼:

/**
     * 編譯java程序,生成Class byte數組
     * @param proxyJavaSource java源程序
     * @return 編譯後的class byte數組
     */
    private static byte[] compailerJavaSource(String proxyJavaSource) throws Exception {
        String name = "Proxy0";
        URI uri = URI.create("string:///" + name.replace('.', '/') + JavaFileObject.Kind.SOURCE.extension);
        MyJavaFile javafile = new MyJavaFile(uri, JavaFileObject.Kind.SOURCE);
        javafile.setContent(proxyJavaSource);
        List<JavaFileObject> javaFileObjects = Lists.newArrayList(javafile);

        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        MyJavaFileManager myJavaFileManager =  new MyJavaFileManager(
                compiler.getStandardFileManager(null, null, null));

        JavaCompiler.CompilationTask task = compiler.getTask(
                null, myJavaFileManager, null, null, null, javaFileObjects);
        task.call();
        MyJavaFile javaFile = myJavaFileManager.getByteArrayJavaFileObjects().get(0);
        ByteArrayOutputStream outputStream = (ByteArrayOutputStream) javaFile.openOutputStream();
        return outputStream.toByteArray();
    }

編譯過程中需要定義自己的JavaFile和JavaFileManager:

JavaFile:

public class MyJavaFile extends SimpleJavaFileObject {
   private String content;
    private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    public MyJavaFile(URI uri, Kind kind) {
        super(uri, kind);
    }
    public void setContent(String content){
        this.content = content;
    }

    @Override
    public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
        return content;
    }

    @Override
    public OutputStream openOutputStream() throws IOException {
        return outputStream;
    }
}

JavaFileManager:

public class MyJavaFileManager extends ForwardingJavaFileManager {
    /**
     * Creates a new instance of ForwardingJavaFileManager.
     *
     * @param fileManager delegate to this file manager
     */
    public MyJavaFileManager(JavaFileManager fileManager) {
        super(fileManager);
    }

    private Set<MyJavaFile> javaFiles = new HashSet<>();

    public Set<MyJavaFile> getByteArrayJavaFileObjects() {
        return javaFiles;
    }

    // 有字節碼的輸出的時候 我們自定義一個JavaFileObject 來接受輸出
    @Override
    public JavaFileObject getJavaFileForOutput(Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException {
        if (JavaFileObject.Kind.CLASS == kind) {
            MyJavaFile javaFile = new MyJavaFile(URI.create("bytes:///" + className.replace(".", "/") + ".class"), kind);
            javaFiles.add(javaFile);
            return javaFile;
        } else {
            return super.getJavaFileForOutput(location, className, kind, sibling);
        }
    }
}

3.2.3 加載class

將java源程序編譯成class字節碼數組後,接下來就要對編譯好的class進行加載:

/**
     * 通過構造器加載Class
     * @param classLoader 自定義類加載器
     * @param classBytes class byte數組
     * @return Class對象
     */
    private static Class loadClass(MyClassLoader classLoader, byte[] classBytes) throws Exception {
        Class<?> clazz = classLoader.myLoadClass(classBytes);
        return clazz;
    }

這裏需要定義自己的類加載器,簡單實現一下:

public class MyClassLoader extends ClassLoader {
    public Class<?> myLoadClass(byte[] bytes) throws ClassNotFoundException {
        return defineClass(bytes, 0, bytes.length);
    }
}

3.2.4 創建代理對象

最後就是創建代理類對象了:

/**
     * 創建代理對象
     * @param proxyClass
     * @param invocationHandler
     * @return
     */
    private static Object createdProxyInstance(Class proxyClass, MyInvocationHandler invocationHandler) throws Exception {
        Constructor constructor = proxyClass.getDeclaredConstructor(MyInvocationHandler.class);
        constructor.setAccessible(true);
        Object proxy = constructor.newInstance(invocationHandler);
        return proxy;
    }

3.2.5 測試

到這裏就成功實現了自己的動態代理,下面測試一下功能如何:

定義兩個測試接口:

/**
 * 訂單服務
 */
public interface OrderService {
    /**
     * 創建訂單
     * @throws InterruptedException
     */
    void createOrder();


    /**
     * 查詢訂單
     */
    String createOrder(int a);
}
/**
 * 支付服務
 */
public interface PaymentService {
    /**
     * 支付
     */
    void pay();
}

定義一個實現類,同時實現這兩個接口:

/**
 * 訂單服務
 */
public class OrderServiceImpl implements OrderService, PaymentService {

    @Override
    public void createOrder() {
        System.out.println("創建訂單");
    }

    @Override
    public String createOrder(int a) {
        System.out.println("接受到參數 :a = " + a);
        return "返回結果數據";
    }

    @Override
    public void pay() {
        System.out.println("開始支付了");
    }
}

實現自己的InvocationHandler:

public class MyInvocationHandlerImpl implements MyInvocationHandler {
    private Object object;
    public MyInvocationHandlerImpl(Object object){
        this.object = object;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)  {
        try {
            System.out.println("開始調用了");
            Object invoke = method.invoke(object, args);
            System.out.println("調用結束了");
            return invoke;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

具體測試如下,發現創建動態代理類的過程是不是和Jdk的很類似。

public class MyProxyTest {
    public static void main(String[] args) throws Exception {
        //動態創建自己的代理類
        OrderService orderService = (OrderService) MyProxy.newProxyInstance(
                new MyClassLoader(),
                OrderServiceImpl.class.getInterfaces(),
                new MyInvocationHandlerImpl(new OrderServiceImpl()));
        //調用createOrder
        orderService.createOrder();
        //調用帶參數的createOrder
        String result = orderService.createOrder(3);
        System.out.println(result);

        //再次動態創建代理類
        PaymentService paymentService = (PaymentService) MyProxy.newProxyInstance(new MyClassLoader(),
                OrderServiceImpl.class.getInterfaces(), new MyInvocationHandlerImpl(new OrderServiceImpl()));
        //調用pay
        paymentService.pay();
    }
}

測試結果如下:

在這裏插入圖片描述

結果也符合預期,到這裏就完成了自己動態代理類的實現。

4 CGlib動態代理原理分析

4.1 CGlib使用

先看看CgLib動態代理是如何使用的:

首先要實現MethodInterceptor接口

public class CglibPrintTimeProxyInterceptor implements MethodInterceptor {
    /**
     * 
     * @param obj  被代理對象
     * @param method 要執行的方法
     * @param args 要執行的方法參數列表
     * @param proxy 
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("調用前執行");
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("調用後執行");
        return result;
    }
}

MethodInterceptor和Jdk動態代理中InvocationHandler相似,但是此處有一個很大的不同的地方,就是此時調用實際的被代理類的方法的時候,使用的是proxy.invokeSuper(obj, args);,很明顯是調用的是父類的方法。

創建代理類並調用:

public class CglibTest {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(OrderServiceImpl.class);
        enhancer.setCallback(new CglibPrintTimeProxyInterceptor());
        //創建代理類對象
        OrderService orderService = (OrderServiceImpl)enhancer.create();
        
        orderService.createOrder();
    }
}

執行結果如下:
在這裏插入圖片描述

爲了清楚的瞭解Cglib的實現原理,可以添加如下代碼,就可以打印出動態代理生成的代理類Class:
在這裏插入圖片描述

System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,
                "F:\\document\\me\\java\\cglib_proxy_class");

執行之後,在文件夾中,找到生成動態代理類的地方,發現生成了四個class文件:
[外鏈圖片轉存失敗(img-Prp4KJTU-1567009203883)(DAB490962E0E47DC9695F13589B1FE7F)]

可以通過執行程序打印類型方式找到生成的代理類class,然後對其反編譯:
在這裏插入圖片描述

發現代理類繼承了被代理類,重寫代理類中createOrder方法實現如下:

public final void createOrder() {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        if (var10000 != null) {
            var10000.intercept(this, CGLIB$createOrder$0$Method, CGLIB$emptyArgs, CGLIB$createOrder$0$Proxy);
        } else {
            super.createOrder();
        }
    }

發現,調用代理類的createOrder,實際調用的還是MethodInterceptor的intercept方法,然後在MethodInterceptor的intercept中調用的是父類的方法,也就是被代理類的createOrder方法。

這裏,其實只是簡單的分析了一下Cglib的原理,其實其粗略的原理就是這樣:

  1. 動態創建的代理類,繼承了被代理類並實現了其方法
  2. 代理類中的方法實際調用的是MethodInterceptor的intercept方法。
  3. MethodInterceptor的intercept方法中會調用代理類父類的相應方法,父類就是被代理類。

但是CGlib真正的實現,要比這複雜的多,本人也沒有做過多的研究, 但是下面簡單說一個細節,爲了後面 jdk動態代理和CGlib動態代理比較做鋪墊。

MethodInterceptor中調用intercept方法的時候,調用的是MethodProxy對象的invokeSuper方法。

在這裏插入圖片描述

invokeSuper方法實現如下:
在這裏插入圖片描述

其中fci.f2是FastClass類型:

在這裏插入圖片描述

其實FastClass也是動態生成的,我們前面打印cglib動態生成的class的時候,不是發現有四個class,其中有兩個是FastClass。
在這裏插入圖片描述

重點來了:這裏的兩個動態生成的FastClass,只有當調用動態代理類被代理的方法的時候,纔會動態生成。如果只是創建代理類而不調用被代理的方法,是不會動態創建的。

測試如下:

public class CglibTest {
    public static void main(String[] args) {

        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,
                "F:\\document\\me\\java\\cglib_proxy_class");

        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(OrderServiceImpl.class);
        enhancer.setCallback(new CglibPrintTimeProxyInterceptor());
        //創建代理類對象
        OrderService orderService = (OrderServiceImpl)enhancer.create();

//        orderService.createOrder();
    }
}

打印生成的class如下,發現,確實沒有生成FastClass類。

[外鏈圖片轉存失敗(img-Az72oukl-1567009203885)(45113C63958F4C62BD90C20885587E27)]

好了,到這裏,cglib的實現分析告一段落,更細節的我也沒有再研究,想研究的,可以自行繼續研究實現細節。

5 jdk動態代理和CGlib動態代理比較

Jdk動態代理:被代理對象必須實現接口,不能代理沒有接口的類。

cglib動態代理:對類本身沒有限制,但是cglib有個坑,就是不能代理final方法,因爲繼承不能重寫final方法,這個在使用中要注意。

那實際中我們應該選擇jdk還是cglib?

這個需要兩個因素,一個是代理本身的侷限性,二是代理的性能問題。

代理本身的侷限性已經提到了,jdk代理的對象必須要有接口,所以相比,cglib功能更強大。

對於性能問題,因爲jdk和cglib都是用到了反射, 是比較耗性能的,所以需要寫代碼去測試,主要測試兩個點,一個是代理類的創建性能,一個是執行方法的性能。

這裏使用的jdk版本是1.8, cglib版本是3.2.1,不管是創建對象還是執行方法,我們都會先執行預熱一下,原因是前面提到的,Cglib有的動態Class,不是在創建代理類的時候就生成了,而是在方法具體的執行的時候才創建的,所以會導致第一次執行會比較耗時。

5.1 創建性能比較

這裏比較創建的性能,是模仿爲大量不同的對象創建代理類,而非多次爲同一個對象創建不同的代理類,因爲多次爲同一個對象創建代理類,JDK和CGLib對其會進行優化,不能夠看出明顯的區別。並且在Spring中,所有的代理類都是代理的,也並不需要重複爲同一個類創建多次代理。

這裏主要爲三個類創建代理,OrderService, PaymentService,InvoiceService三個類進行測試:具體測試代碼如下:

public class JdkCglibCreateCompiler {
    public static void main(String[] args) {
        Long jdkStart1 = System.currentTimeMillis();
        OrderService jdkOrderServiceProxy = (OrderService) Proxy.newProxyInstance(
                OrderService.class.getClassLoader(),
                OrderServiceImpl.class.getInterfaces(),
                new JdkInvocationHandler(new OrderServiceImpl()));
        Long jdkEnd1 = System.currentTimeMillis();
        System.out.println("JDK創建第一個代理類 : " + (jdkEnd1 - jdkStart1));

        Long jdkStart2 = System.currentTimeMillis();
        PaymentService jdkPaymentServiceProxy = (PaymentService) Proxy.newProxyInstance(
                PaymentService.class.getClassLoader(),
                PaymentServiceImpl.class.getInterfaces(),
                new JdkInvocationHandler(new PaymentServiceImpl()));
        Long jdkEnd2 = System.currentTimeMillis();
        System.out.println("JDK創建第二個代理類 : " + (jdkEnd2 - jdkStart2));

        Long jdkStart3 = System.currentTimeMillis();
        InvoiceService jdkPaymentServiceProxy = (InvoiceService) Proxy.newProxyInstance(
                InvoiceService.class.getClassLoader(),
                InvoiceServiceImpl.class.getInterfaces(),
                new JdkInvocationHandler(new InvoiceServiceImpl()));
        Long jdkEnd3 = System.currentTimeMillis();
        System.out.println("JDK創建第三個代理類 : " + (jdkEnd3 - jdkStart3));


        ///******************Cglib*********************
        Long cglibStart1 = System.currentTimeMillis();
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(OrderServiceImpl.class);
        enhancer.setCallback(new CglibPrintTimeProxyInterceptor());
        //創建代理類對象
        OrderService orderService = (OrderServiceImpl)enhancer.create();
        Long cglibEnd1 = System.currentTimeMillis();
        System.out.println("CGLIB創建第一個代理類 : " + (cglibEnd1 - cglibStart1));

        Long cglibStart2 = System.currentTimeMillis();
        enhancer.setSuperclass(PaymentServiceImpl.class);
        enhancer.setCallback(new CglibPrintTimeProxyInterceptor());
        //創建代理類對象
        PaymentService paymentService = (PaymentService)enhancer.create();
        Long cglibEnd2 = System.currentTimeMillis();
        System.out.println("CGLIB創建第二個代理類 : " + (cglibEnd2 - cglibStart2));

        Long cglibStart3 = System.currentTimeMillis();
        enhancer.setSuperclass(InvoiceServiceImpl.class);
        enhancer.setCallback(new CglibPrintTimeProxyInterceptor());
        //創建代理類對象
        InvoiceService invoiceService = (InvoiceService)enhancer.create();
        Long cglibEnd3 = System.currentTimeMillis();
        System.out.println("CGLIB創建第三個代理類 : " + (cglibEnd3 - cglibStart3));
    }
}

因爲只有三個測試類,爲了模擬創建大量創建不同的對象,我這裏上面代碼重複執行40次,也就是jdk和cglib各自創建120個對象,得到如下結果:
在這裏插入圖片描述
從圖中能夠看出,不管是jdk還是cglib,第一次創建對象都會比較耗時,這裏把第一次作爲預熱,只考慮後兩次,也就是各自創建80個對象,對其進行加和,結果是Jdk創建80個對象,需要168ms,平均每個2.1ms, Cglib創建80個對象需要227ms,平均每個2.8ms。
結論是:Jdk1.8創建代理類對象的效率略高於cglib3.2.1

5.2 執行性能

這裏採用先預熱10次,然後各自循環執行5萬次,取執行5萬次用時:

public class JdkCglibExecCompiler {
    public static void main(String[] args) {
        OrderService jdkOrderServiceProxy = (OrderService) Proxy.newProxyInstance(
                OrderService.class.getClassLoader(),
                OrderServiceImpl.class.getInterfaces(),
                new JdkInvocationHandler(new OrderServiceImpl()));
        //預熱
        for (int i = 0; i < 10; i++){
           jdkOrderServiceProxy.createOrder();
        }
        Long jdkStart1 = System.currentTimeMillis();
        for (int i = 0; i < 50000; i++){
            jdkOrderServiceProxy.createOrder();
        }
        Long jdkEnd1 = System.currentTimeMillis();
        System.out.println("jdk創建代理類 : " + (jdkEnd1 - jdkStart1));
        System.out.println((jdkEnd1 - jdkStart1));


        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(OrderServiceImpl.class);
        enhancer.setCallback(new CglibPrintTimeProxyInterceptor());
        OrderService cglibOrderServiceProxy = (OrderServiceImpl)enhancer.create();
        //預熱
        for (int i = 0; i < 10; i++){
            cglibOrderServiceProxy.createOrder();
        }
        Long cglibStart1 = System.currentTimeMillis();
        for (int i = 0; i < 50000; i++){
            cglibOrderServiceProxy.createOrder();
        }
        Long cglibEnd1 = System.currentTimeMillis();
        System.out.println("cglib創建代理類 : " + (cglibEnd1 - cglibStart1));
    }
}

爲了防止單次執行5萬次不具有普遍性,這裏執行10次,取每次執行5萬次的時間如下:
在這裏插入圖片描述
結論:Jdk1.8代理類執行效率略高於cglib3.2.1

最後說一點,Spring中默認使用的是Jdk代理,但是可以再配置文件中配置實用其他代理方式。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章