《豬弟拱Java》連載番外篇:Java代理(中)

glmapper_2018發表於2018-01-16

在上一篇中我用一個比較詳細的案例描述了一下代理的概念,在這篇文章中,主要來看一下JDK動態代理和cglib子類代理

JDK動態代理

首先我們以一個簡單的案例來說一下

案例:

現在有這樣一個需求,為一個簡訊功能提供入參日誌列印、異常列印和處理、返回結果列印、方法呼叫結束列印。先來看一下簡訊功能的程式碼:

首先是簡訊介面ISmsSupport

package proxy;

/**
 * 簡訊功能支援
 *
 * @author 豬弟
 * @since v1.0.0
 */
public interface ISmsSupport {

    boolean sendMsg(String content, String phoneNumber);
}

複製程式碼

然後是一個簡訊功能的實現類

package proxy;
/**
 * 簡訊功能實現
 *
 * @author 豬弟
 * @since v1.0.0
 */
public class SmsSupportImpl implements ISmsSupport {
    @Override
    public boolean sendMsg(String content, String phoneNumber) {
        //模擬異常
        int temp = 1 / 0;
        System.out.println(content + "," + phoneNumber);
        return true;
    }
}

複製程式碼

在實現類中,我們沒有做任何的日誌和異常處理 接下來我們用動態代理實現上面的需求,以及為什麼要用動態代理實現? 首先我們要建一個代理類SmsProxy

package proxy;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

/**
 * 簡訊功能代理類
 *
 * @author 豬弟
 * @since v1.0.0
 */
public class SmsProxy implements InvocationHandler {

    /**
     * 日誌例項
     */
    private final static Logger LOGGER = LoggerFactory.getLogger(SmsProxy.class);
    
    /**
     * 被代理物件
     */
    private Object realSubject;

    /**
     * 構造器
     *
     * @param realSubject 被代理物件
     */
    public SmsProxy(Object realSubject) {
        this.realSubject = realSubject;
    }

    /**
     * 獲取代理物件的方法
     *
     * @return 代理物件
     */
    public Object getProxy() {
        Class<?> subjectClass = realSubject.getClass();
        return Proxy.newProxyInstance(subjectClass.getClassLoader(), subjectClass.getInterfaces(), this);
    }

    /**
     * @param proxy  代理物件
     * @param method 執行的目標方法
     * @param args   方法引數
     * @return Object 目標方法執行的結果
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //目標方法的方法名
        String methodName = method.getName();

        //列印入參(前置通知)
        LOGGER.info("{} 方法的入參:{}", methodName, args);
        try {

            //反射Reflect執行核心方法
            Object result = method.invoke(realSubject, args);

            //列印執行結果(返回後通知)
            LOGGER.info("{}方法的返回結果為:{}", methodName,result);

            return result;
        } catch (Throwable e) {

            //列印異常日誌(異常通知)
            LOGGER.error("執行目標方法發生異常,異常:", e);
            return Boolean.FALSE;
        } finally {

            //方法執行完列印(後置通知)
            LOGGER.info("方法執行完成");
        }
    }
}

複製程式碼

簡單解釋一下這個代理類

① 首先使用jdk動態代理要實現一個java為我們提供的InvocationHandler介面,並實現invoke方法,下面是介面的原始碼。

public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}
複製程式碼

② 我們要在代理類中定義一個Object型別的屬性用於引用被代理的物件,並通過構造器初始化。

③ 在類中定義一個函式getProxy用於獲取代理物件,這裡用到了Proxy類的靜態方法newInstance,下面是方法的原始碼內容:

    @CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        //要求傳入的handler不能為空
        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.
         * 使用指定的invocation handler 反射呼叫代理類的構造器
         */
        try {
            if (sm != null) {
                //檢查訪問許可權
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }
            //獲取代理類的構造器物件
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            //傳入的invocation handler
            final InvocationHandler ih = h;
            //如果類不是public的,先賦予許可權
            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);
        }
    }
複製程式碼

④ 最後就是實現我們的invoke方法了,先看一下方法的結構和我們的實現:

    /**
     * @param proxy  代理物件
     * @param method 執行的目標方法
     * @param args   方法引數
     * @return Object 目標方法執行的結果
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //目標方法的方法名
        String methodName = method.getName();
        //列印入參(前置通知)
        LOGGER.info("{} 方法的入參:{}", methodName, args);
        try {
            //反射Reflect執行核心方法
            Object result = method.invoke(realSubject, args);
            //列印執行結果(返回後通知)
            LOGGER.info("{}方法的返回結果為:{}", methodName,result);
            return result;
        } catch (Throwable e) {
            //列印異常日誌(異常通知)
            LOGGER.error("執行目標方法發生異常,異常:", e);
            return Boolean.FALSE;
        } finally {
            //方法執行完列印(後置通知)
            LOGGER.info("方法執行完成");
        }
    }
複製程式碼

是不是很簡單呢,下面是入口程式:

package proxy;

/**
 * 程式入口
 *
 * @author 豬弟
 * @since v1.0.0
 */
public class Bootstrap {

    public static void main(String[] args){
        demo1();
    }

    public static void demo1(){
        ISmsSupport smsSupport = new SmsSupportImpl();
        ISmsSupport proxy = (ISmsSupport) new SmsProxy(smsSupport).getProxy();
        proxy.sendMsg("hello world", "110");
    }

}

複製程式碼

我們先看一下執行結果 先把SmsSupportImpl中的int i = 1/ 0;註釋掉正常執行:

17:12:26.976 [main] INFO proxy.SmsProxy - sendMsg 方法的入參:[hello world, 110]
hello world,110
17:12:26.980 [main] INFO proxy.SmsProxy - sendMsg方法的返回結果為:true
17:12:26.980 [main] INFO proxy.SmsProxy - 方法執行完成
複製程式碼

再取消註釋,發生異常:

17:13:31.169 [main] INFO proxy.SmsProxy - sendMsg 方法的入參:[hello world, 110]
17:13:31.175 [main] ERROR proxy.SmsProxy - 執行目標方法發生異常,異常:
java.lang.reflect.InvocationTargetException: null
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at proxy.SmsProxy.invoke(SmsProxy.java:63)
	at com.sun.proxy.$Proxy0.sendMsg(Unknown Source)
	at proxy.Bootstrap.demo1(Bootstrap.java:19)
	at proxy.Bootstrap.main(Bootstrap.java:12)
Caused by: java.lang.ArithmeticException: / by zero
	at proxy.SmsSupportImpl.sendMsg(SmsSupportImpl.java:13)
	... 8 common frames omitted
17:13:31.175 [main] INFO proxy.SmsProxy - 方法執行完成
複製程式碼

從上面的執行結果可以看出,列印了入參和返回結果,正確攔截了異常並列印了異常資訊,還有方法呼叫完成的日誌。好啦!到這裡動態代理的實現就結束了,接下來看看為什麼要使用動態代理。

在這個案例中其實體現不出來動態代理的優勢,為什麼呢,因為要列印日誌的類太少了,完全可以採用硬編碼的方式去實現,那麼想象一下有一萬個類,每個類中有10個方法,每個方法之前都沒有打日誌,現在要你去實現為每個方法都實現入參和返回結果列印,你還會採用硬編碼的方式嗎?此時動態代理就發揮了作用,你只需要寫一個日誌代理類,專門用來完成這個列印功能,上程式碼:

package proxy;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

/**
 * 日誌代理列印類
 *
 * @author 豬弟
 * @since v1.0.0
 */
public class LogProxy<T> implements InvocationHandler {

    /**
     * 日誌例項
     */
    private final static Logger LOGGER = LoggerFactory.getLogger(LogProxy.class);

    /**
     * 被代理物件
     */
    private T realSubject;

    /**
     * 構造器
     *
     * @param realSubject
     */
    public LogProxy(T realSubject) {
        this.realSubject = realSubject;
    }

    /**
     * 獲取代理物件
     *
     * @return 代理
     */
    public T getProxy() {
        Class<?> subjectClass = realSubject.getClass();
        return (T) Proxy.newProxyInstance(subjectClass.getClassLoader(), subjectClass.getInterfaces(), this);
    }

    /**
     * @param proxy  代理物件
     * @param method 執行的目標方法
     * @param args   方法引數
     * @return Object 目標方法執行結果
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //目標方法的方法名
        String methodName = method.getName();
        //列印入參(前置通知)
        LOGGER.info("{} 方法的入參:{}", methodName, args);
        try {
            //執行核心方法
            Object result = method.invoke(realSubject, args);
            //列印執行結果(返回後通知)
            LOGGER.info("{}方法的返回結果為:{}", methodName,result);
            return result;
        } catch (Throwable e) {
            //列印異常日誌(異常通知)
            LOGGER.error("執行目標方法發生異常,異常:", e);
            return Boolean.FALSE;
        } finally {
            //方法執行完列印(後置通知)
            LOGGER.info("方法執行完成");
        }
    }
}

複製程式碼

其實這個類和前面的類區別不大,只是加入了泛型和把getProxy的返回型別改成了泛型。 呼叫方式和前面的類似,結果也一樣,入口程式:

package proxy;

/**
 * 程式入口
 *
 * @author 豬弟
 * @since v1.0.0
 */
public class Bootstrap {
    public static void main(String[] args){
        demo2();
    }
    public static void demo2() {
        ISmsSupport smsSupport = new SmsSupportImpl();
        LogProxy<ISmsSupport> proxy = new LogProxy<>(smsSupport);
        ISmsSupport smsProxy = proxy.getProxy();
        smsProxy.sendMsg("hello world", "110");
    }
}

複製程式碼

其實你會發現,雖然書寫方便了,但是去修改呼叫方法的程式碼也是一件工作量很大的事情,我們可以採用AOP來處理,在AOP中採用切入點表示式可以不用修改呼叫方法的程式碼就能實現使用代理列印日誌。關於AOP留到下一篇中去講述。

好的,我們再來看看生成的代理類長什麼樣子,怎麼看呢?當然是把記憶體中的物件存到硬碟,還記得在Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)裡面呼叫了下面這句程式碼:

    /*
     * Look up or generate the designated proxy class.
     */
    Class<?> cl = getProxyClass0(loader, intfs);
複製程式碼

看看這個方法的原始碼:

    /**
     * Generate a proxy class.  Must call the checkProxyAccess method
     * to perform permission checks before calling this.
     */
    private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }

        // If the proxy class defined by the given loader implementing
        // the given interfaces exists, this will simply return the cached copy;
        // otherwise, it will create the proxy class via the ProxyClassFactory
        return proxyClassCache.get(loader, interfaces);
    }
    /**
     * a cache of proxy classes
     */
    private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
        proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
複製程式碼

在Proxy類中有個屬性proxyClassCache,這是一個WeakCache型別的靜態變數。它指示了類載入器和代理類之間的對映。所以proxyClassCache的get方法用於根據類載入器和介面陣列來獲取Proxy類,如果已經存在則直接從cache中返回,如果沒有則建立一個對映並更新cache表。

我們跟一下代理類的建立流程: 呼叫Factory類的get方法,而它又呼叫了ProxyClassFactory類的apply方法,最終找到下面一行程式碼:

    public static byte[] generateProxyClass(String var0, Class<?>[] var1) {
        return generateProxyClass(var0, var1, 49);
    }
複製程式碼

我們可以使用下面的程式碼來生成代理類的class檔案到磁碟:

public static void main(String[] args) throws IOException {
    /**
     * @param s 生成檔案的名字
     * @param classes 實現的介面陣列
     * @return byte[] 類檔案的位元組碼陣列
     */
    byte[] classFile = ProxyGenerator.generateProxyClass("SmsSupportProxy",
            SmsSupportImpl.class.getInterfaces());
    File file = new File("D:/SmsSupportProxy.class");
    FileOutputStream fos = new FileOutputStream(file);
    fos.write(classFile);
    fos.flush();
    fos.close();
}
複製程式碼

執行生成一個SmsSupportProxy.class檔案,用IDEA開啟得到反編譯的內容:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import proxy.ISmsSupport;

public final class SmsSupportProxy extends Proxy implements ISmsSupport {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

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

    public final boolean equals(Object var1) throws  {
        try {
            return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();
        } 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 boolean sendMsg(String var1, String var2) throws  {
        try {
            return ((Boolean)super.h.invoke(this, m3, new Object[]{var1, var2})).booleanValue();
        } catch (RuntimeException | Error var4) {
            throw var4;
        } catch (Throwable var5) {
            throw new UndeclaredThrowableException(var5);
        }
    }

    public final int hashCode() throws  {
        try {
            return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();
        } 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("proxy.ISmsSupport").getMethod("sendMsg", Class.forName("java.lang.String"), Class.forName("java.lang.String"));
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}
複製程式碼

這樣,結合上面的handler,就很好明白為什麼代理類在呼叫目標方法時會執行invoke了吧

cglib子類代理

在上一篇文章末尾我們介紹了三種代理的使用條件和使用限制,來回顧一下

① 靜態代理可以代理某一類物件,這一類物件必須實現同一介面,所以它的使用條件就是被代理類要實現介面。上面的各種場景簡訊都實現了ISmsService介面,所以代理類可以代理所有場景簡訊實現類,並呼叫真正的簡訊傳送方法去傳送正確的場景簡訊。

① 動態代理的使用條件也是被代理類要實現介面,但是動態代理能夠代理所有實現了介面的類,強大必然也會有缺點:動態代理依賴Java反射機制,反射是一個比較影響效能的功能,所以動態代理效能上會比靜態代理低。

③ cglib子類代理,首先需要依賴第三方庫,然後它是基於位元組碼來生成子類代理的,沒有特定的使用條件,所以也不需要實現介面,它可以代理所有的類,所以論效能是比不上靜態代理的。

從上面可以看出,前兩種代理都需要實現介面才能使用,而CGLIB不用

此處留一個面試中遇到的問題:

jdk代理中為什麼一定要實現介面呢,用抽象類不可以嗎?用心思考一下,相信和我一樣聰明的你一定能得到答案

好的,我們把問題留到下一篇中解答

OK,接下來我們還是同樣的一個需求,使用CGLIB代理的方式實現,如果你理解了上面動態代理的內容,CGLIB的內容就能輕鬆的理解了,這裡我們只學習使用方式,關於原理他是根據位元組碼動態生成的子類,豬弟的水平還達不到就不具體解釋了,待日後我豬拱神功練成之時在來講述原理。

先看一下簡訊傳送類,沒有實現任何介面:

package xin.sun.proxy.cglib;

/**
 * 簡訊功能實現
 *
 * @author 豬弟
 * @since v1.0.0
 */
public class SmsSupport {

    public boolean sendMsg(String content, String phoneNumber) {
        //模擬異常
        //int temp = 1 / 0;
        System.out.println(content + "," + phoneNumber);
        return true;
    }
}
複製程式碼

再看一下代理工廠類:

引入了 spring 的 maven 依賴:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>4.3.10.RELEASE</version>
</dependency>
複製程式碼
package xin.sun.proxy.cglib;

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

/**
 * @author 豬弟
 */
public class ProxyFactory implements MethodInterceptor {
    /**
     * 被代理物件
     */
    private Object target;
    /**
     * 構造器
     *
     * @param target
     */
    public ProxyFactory(Object target) {
        this.target = target;
    }
    /**
     * 獲取代理物件例項
     *
     * @return Object 代理物件例項
     */
    public Object getProxyInstance() {
        /**
         * 工具類
         */
        Enhancer enhancer = new Enhancer();
        /**
         * 設定父類
         */
        enhancer.setSuperclass(target.getClass());
        /**
         * 設定回撥物件
         */
        enhancer.setCallback(this);
        /**
         * 建立子類代理物件,並返回
         */
        return enhancer.create();
    }

    /**
     * 重寫攔截方法
     *
     * @param o           代理物件
     * @param method      委託類方法
     * @param objects     方法引數
     * @param methodProxy 代理方法的MethodProxy物件
     * @return Object 目標方法執行結果
     * @throws Throwable
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        //目標方法的方法名
        String methodName = method.getName();
        //列印入參(前置通知)
        LOGGER.info("{} 方法的入參:{}", methodName, objects);
        try {
            //執行核心方法
            Object result = method.invoke(realSubject, objects);
            //列印執行結果(返回後通知)
            LOGGER.info("{}方法的返回結果為:{}", methodName, result);
            return result;
        } catch (Throwable e) {
            //列印異常日誌(異常通知)
            LOGGER.error("執行目標方法發生異常,異常:", e);
            return Boolean.FALSE;
        } finally {
            //方法執行完列印(後置通知)
            LOGGER.info("方法執行完成");
        }
    }
}
複製程式碼

簡單解釋一下這個代理類

① 首先使用cglib代理要實現一個框架為我們提供的MethodInterceptor介面,並實現intercept方法。

② 我們要在代理類中定義一個Object型別的屬性用於引用被代理的物件,並通過構造器初始化。

③ 在類中定義一個函式getProxyInstance用於獲取代理物件,這裡和jdk動態代理有點區別,這裡使用的是Enhancer這個類,看一下方法的實現:

    public Object getProxyInstance() {
        /**
         * Enhancer工具類
         */
        Enhancer enhancer = new Enhancer();
        /**
         * 設定父類
         */
        enhancer.setSuperclass(realSubject.getClass());
        /**
         * 設定回撥物件,我們實現的這個類本身就是實現了介面的,所以傳本類的例項就好了
         */
        enhancer.setCallback(this);
        /**
         * 建立子類代理物件,並返回
         */
        return enhancer.create();
    }
複製程式碼

④ 最後就是實現我們的intercept方法了,先看一下方法的結構和我們的實現:

    /**
     * 重寫攔截方法
     *
     * @param o           代理物件
     * @param method      委託類方法
     * @param objects     方法引數
     * @param methodProxy 代理方法的MethodProxy物件
     * @return Object 目標方法執行結果
     * @throws Throwable
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        //目標方法的方法名
        String methodName = method.getName();
        //列印入參(前置通知)
        LOGGER.info("{} 方法的入參:{}", methodName, objects);
        try {
            //執行核心方法
            Object result = method.invoke(realSubject, objects);
            //列印執行結果(返回後通知)
            LOGGER.info("{}方法的返回結果為:{}", methodName, result);
            return result;
        } catch (Throwable e) {
            //列印異常日誌(異常通知)
            LOGGER.error("執行目標方法發生異常,異常:", e);
            return Boolean.FALSE;
        } finally {
            //方法執行完列印(後置通知)
            LOGGER.info("方法執行完成");
        }
    }
複製程式碼

同樣的,我們寫一個入口程式來檢測一下:

package xin.sun.proxy.cglib;

/**
 * @author 豬弟
 */
public class Bootstrap {

    public static void main(String[] args) {
        demo();
    }

    public static void demo(){
        ProxyFactory proxyFactory = new ProxyFactory(new SmsSupport());
        SmsSupport proxyInstance = (SmsSupport) proxyFactory.getProxyInstance();
        proxyInstance.sendMsg("CGLIB 代理","4008008820");
    }
}

複製程式碼

看看正常執行的結果:

22:06:26.439 [main] INFO xin.sun.proxy.cglib.ProxyFactory - sendMsg 方法的入參:[CGLIB 代理, 4008008820]
CGLIB 代理,4008008820
22:06:26.445 [main] INFO xin.sun.proxy.cglib.ProxyFactory - sendMsg方法的返回結果為:true
22:06:26.445 [main] INFO xin.sun.proxy.cglib.ProxyFactory - 方法執行完成
複製程式碼

同樣的,我們人為製造一個異常int temp = 1 / 0;,看看異常能否被捕獲:

22:10:36.454 [main] INFO xin.sun.proxy.cglib.ProxyFactory - sendMsg 方法的入參:[CGLIB 代理, 4008008820]
22:10:36.460 [main] ERROR xin.sun.proxy.cglib.ProxyFactory - 執行目標方法發生異常,異常:
java.lang.reflect.InvocationTargetException: null
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at xin.sun.proxy.cglib.ProxyFactory.intercept(ProxyFactory.java:80)
	at xin.sun.proxy.cglib.SmsSupport$$EnhancerByCGLIB$$8593c815.sendMsg(<generated>)
	at xin.sun.proxy.cglib.Bootstrap.demo(Bootstrap.java:15)
	at xin.sun.proxy.cglib.Bootstrap.main(Bootstrap.java:9)
Caused by: java.lang.ArithmeticException: / by zero
	at xin.sun.proxy.cglib.SmsSupport.sendMsg(SmsSupport.java:13)
	... 8 common frames omitted
22:10:36.461 [main] INFO xin.sun.proxy.cglib.ProxyFactory - 方法執行完成
複製程式碼

從結果可以看到,完美的捕獲到了異常

可以看到,在cglib中的簡訊傳送類沒有實現介面,同樣的也實現了代理的功能,所以cglib才是真的能代理所有的類,這也是對jdk動態代理的一個補充吧,但是在Spring的AOP中預設是使用jdk動態代理的,如果被代理類沒有實現介面,Spring會自動為我們切換cglib子類代理,是不是覺得Spring很人性化,其實 Spring AOP 為我們提供了更加方便的方式去做代理,下一篇文章會講述Spring中的AOP程式設計。

Spring AOP 把代理做了一次封裝,用起來當然更加方便了,但是要想成為java大神,還是要追究其實質的,這也是為什麼我要寫這兩篇文章的原因,所有的框架都是基於Java基礎+程式設計思想來實現的,其實明白了框架的設計,我們自己也能實現Spring IOC DI AOP,等有機會為大家手擼一波Spring框架,當然Spring是高度的抽象,所以原始碼閱讀起來沒那麼容易,但是其思想還是很容易懂的,學會思想才是真的叼,畢竟思想語言無關。

磊叔是Spring專家,感興趣的多看看磊叔的部落格哦!!!

為了提供更加方便的閱讀,小夥伴們可以關注我們的公眾微訊號哦...

《豬弟拱Java》連載番外篇:Java代理(中)

相關文章