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

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

JDK動態代理

首先咱們以一個簡單的案例來講一下面試

案例:

如今有這樣一個需求,爲一個短信功能提供入參日誌打印、異常打印和處理、返回結果打印、方法調用結束打印。先來看一下短信功能的代碼:spring

首先是短信接口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("方法執行完成");
        }
    }
}

複製代碼

簡單解釋一下這個代理類bash

① 首先使用jdk動態代理要實現一個java爲咱們提供的InvocationHandler接口,並實現invoke方法,下面是接口的源碼。微信

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

② 咱們要在代理類中定義一個Object類型的屬性用於引用被代理的對象,並經過構造器初始化。app

③ 在類中定義一個函數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專家,感興趣的多看看磊叔的博客哦!!!

爲了提供更加方便的閱讀,小夥伴們能夠關注咱們的公衆微信號哦...

相關文章
相關標籤/搜索