計算機程序的思惟邏輯 (86) - 動態代理

本系列文章經補充和完善,已修訂整理成書《Java編程的邏輯》(馬俊昌著),由機械工業出版社華章分社出版,於2018年1月上市熱銷,讀者好評如潮!各大網店和書店有售,歡迎購買:京東自營連接 html

前面兩節,咱們介紹了反射註解,利用它們,能夠編寫通用靈活的程序,本節,咱們來探討Java中另一個動態特性 - 動態代理。java

動態代理是一種強大的功能,它能夠在運行時動態建立一個類,實現一個或多個接口,能夠在不修改原有類的基礎上動態爲經過該類獲取的對象添加方法、修改行爲,這麼描述比較抽象,下文會具體介紹,這些特性使得它普遍應用於各類系統程序、框架和庫中,好比Spring, Hibernate, MyBatis, Guice等。git

動態代理是實現面向切面的編程(AOP - Aspect Oriented Programming)的基礎,切面的例子有日誌、性能監控、權限檢查、數據庫事務等,它們在程序的不少地方都會用到,代碼都差很少,但與某個具體的業務邏輯關係也不太密切,若是在每一個用到的地方都寫,代碼會很冗餘,也難以維護,AOP將這些切面與主體邏輯相分離,代碼簡單優雅的多。程序員

和註解相似,在大部分的應用編程中,咱們不須要本身實現動態代理,而只須要按照框架和庫的文檔說明進行使用就能夠了。不過,理解動態代理有助於咱們更爲深入的理解這些框架和庫,也能更好的應用它們,在本身的業務須要時,也能本身實現。github

理解動態代理,咱們首先要了解靜態代理,瞭解了靜態代理後,咱們再來看動態代理。動態代理有兩種實現方式,一種是Java SDK提供的,另一種是第三方庫如cglib提供的,咱們會分別介紹這兩種方式,包括其用法和基本實現原理,理解了基本概念和原理後,咱們來看一個簡單的應用,實現一個極簡的AOP框架。數據庫

靜態代理

咱們首先來看代理,代理是一個比較通用的詞,做爲一個軟件設計模式,它在《設計模式》一書中被提出,基本概念和平常生活中的概念是相似的,代理背後通常至少有一個實際對象,代理的外部功能和實際對象通常是同樣的,用戶與代理打交道,不直接接觸實際對象,雖然外部功能和實際對象同樣,但代理有它存在的價值,好比:編程

  • 節省成本比較高的實際對象的建立開銷,按需延遲加載,建立代理時並不真正建立實際對象,而只是保存實際對象的地址,在須要時再加載或建立
  • 執行權限檢查,代理檢查權限後,再調用實際對象
  • 屏蔽網絡差別和複雜性,代理在本地,而實際對象在其餘服務器上,調用本地代理時,本地代理請求其餘服務器

代理模式的代碼結構也比較簡單,咱們看個簡單的例子,代碼以下:swift

public class SimpleStaticProxyDemo {

    static interface IService {
        public void sayHello();
    }

    static class RealService implements IService {

        @Override
        public void sayHello() {
            System.out.println("hello");
        }
    }

    static class TraceProxy implements IService {
        private IService realService;

        public TraceProxy(IService realService) {
            this.realService = realService;
        }

        @Override
        public void sayHello() {
            System.out.println("entering sayHello");
            this.realService.sayHello();
            System.out.println("leaving sayHello");
        }
    }

    public static void main(String[] args) {
        IService realService = new RealService();
        IService proxyService = new TraceProxy(realService);
        proxyService.sayHello();
    }
}
複製代碼

代理和實際對象通常有相同的接口,在這個例子中,共同的接口是IService,實際對象是RealService,代理是TraceProxy。TraceProxy內部有一個IService的成員變量,指向實際對象,在構造方法中被初始化,對於方法sayHello的調用,它轉發給了實際對象,在調用先後輸出了一些跟蹤調試信息,程序輸出爲:設計模式

entering sayHello
hello
leaving sayHello
複製代碼

咱們在54節介紹過兩種設計模式,適配器和裝飾器,它們與代理模式有點相似,它們的背後都有一個別的實際對象,都是經過組合的方式指向該對象,不一樣之處在於,適配器是提供了一個不同的新接口,裝飾器是對原接口起到了"裝飾"做用,多是增長了新接口、修改了原有的行爲等,代理通常不改變接口。不過,咱們並不想強調它們的差異,能夠將它們看作代理的變體,統一看待。數組

在上面的例子中,咱們想達到的目的是在實際對象的方法調用先後加一些調試語句,爲了在不修改原類的狀況下達到這個目的,咱們在代碼中建立了一個代理類TraceProxy,它的代碼是在寫程序時固定的,因此稱爲靜態代理。

輸出跟蹤調試信息是一個通用需求,能夠想象,若是每一個類都須要,而又不但願修改類定義,咱們須要爲每一個類建立代理,實現全部接口,這個工做就太繁瑣了,若是再有其餘的切面需求呢,整個工做可能又要重來一遍。

這時,就須要動態代理了,主要有兩種方式實現動態代理,Java SDK和第三方庫cglib,咱們先來看Java SDK。

Java SDK動態代理

用法

在靜態代理中,代理類是直接定義在代碼中的,在動態代理中,代理類是動態生成的,怎麼動態生成呢?咱們用動態代理實現前面的例子:

public class SimpleJDKDynamicProxyDemo {

    static interface IService {
        public void sayHello();
    }

    static class RealService implements IService {

        @Override
        public void sayHello() {
            System.out.println("hello");
        }
    }

    static class SimpleInvocationHandler implements InvocationHandler {
        private Object realObj;

        public SimpleInvocationHandler(Object realObj) {
            this.realObj = realObj;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("entering " + method.getName());
            Object result = method.invoke(realObj, args);
            System.out.println("leaving " + method.getName());
            return result;
        }
    }

    public static void main(String[] args) {
        IService realService = new RealService();
        IService proxyService = (IService) Proxy.newProxyInstance(IService.class.getClassLoader(),
                new Class<?>[] { IService.class }, new SimpleInvocationHandler(realService));
        proxyService.sayHello();
    }
}
複製代碼

代碼看起來更爲複雜了,這有什麼用呢?彆着急,咱們慢慢解釋。IService和RealService的定義不變,程序的輸出也沒變,但代理對象proxyService的建立方式變了,它使用java.lang.reflect包中的Proxy類的靜態方法newProxyInstance來建立代理對象,這個方法的聲明以下:

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

它有三個參數:

  • loader表示類加載器,下節咱們會單獨探討它,例子使用和IService同樣的類加載器
  • interfaces表示代理類要實現的接口列表,是一個數組,元素的類型只能是接口,不能是普通的類,例子中只有一個IService
  • h的類型爲InvocationHandler,它是一個接口,也定義在java.lang.reflect包中,它只定義了一個方法invoke,對代理接口全部方法的調用都會轉給該方法

newProxyInstance的返回值類型爲Object,能夠強制轉換爲interfaces數組中的某個接口類型,這裏咱們強制轉換爲了IService類型,須要注意的是,它不能強制轉換爲某個類類型,好比RealService,即便它實際代理的對象類型爲RealService。

SimpleInvocationHandler實現了InvocationHandler,它的構造方法接受一個參數realObj表示被代理的對象,invoke方法處理全部的接口調用,它有三個參數:

  • proxy表示代理對象自己,須要注意,它不是被代理的對象,這個參數通常用處不大
  • method表示正在被調用的方法
  • args表示方法的參數

在SimpleInvocationHandler的invoke實現中,咱們調用了method的invoke方法,傳遞了實際對象realObj做爲參數,達到了調用實際對象對應方法的目的,在調用任何方法先後,咱們輸出了跟蹤調試語句。須要注意的是,不能將proxy做爲參數傳遞給method.invoke,好比:

Object result = method.invoke(proxy, args);
複製代碼

上面的語句會出現死循環,由於proxy表示當前代理對象,這麼調用又會調用到SimpleInvocationHandler的invoke方法。

基本原理

看了上面的介紹是否是更暈了,不要緊,看下Proxy.newProxyInstance的內部就理解了。上面例子中建立proxyService的代碼能夠用以下代碼代替:

Class<?> proxyCls = Proxy.getProxyClass(IService.class.getClassLoader(),
        new Class<?>[] { IService.class });
Constructor<?> ctor = proxyCls.getConstructor(new Class<?>[] { InvocationHandler.class });
InvocationHandler handler = new SimpleInvocationHandler(realService);
IService proxyService = (IService) ctor.newInstance(handler);
複製代碼

分爲三步:

  1. 經過Proxy.getProxyClass建立代理類定義,類定義會被緩存
  2. 獲取代理類的構造方法,構造方法有一個InvocationHandler類型的參數
  3. 建立InvocationHandler對象,建立代理類對象

Proxy.getProxyClass須要兩個參數,一個是ClassLoader,另外一個是接口數組,它會動態生成一個類,類名以$Proxy開頭,後跟一個數字,對於上面的例子,動態生成的類定義以下所示,爲簡化起見,咱們忽略了異常處理的代碼:

final class $Proxy0 extends Proxy implements SimpleJDKDynamicProxyDemo.IService {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;

    public $Proxy0(InvocationHandler paramInvocationHandler) {
        super(paramInvocationHandler);
    }

    public final boolean equals(Object paramObject) {
        return ((Boolean) this.h.invoke(this, m1,
                new Object[] { paramObject })).booleanValue();
    }

    public final void sayHello() {
        this.h.invoke(this, m3, null);
    }

    public final String toString() {
        return (String) this.h.invoke(this, m2, null);
    }

    public final int hashCode() {
        return ((Integer) this.h.invoke(this, m0, null)).intValue();
    }

    static {
        m1 = Class.forName("java.lang.Object").getMethod("equals",
                new Class[] { Class.forName("java.lang.Object") });
        m3 = Class.forName("laoma.demo.proxy.SimpleJDKDynamicProxyDemo$IService")
                .getMethod("sayHello",new Class[0]);
        m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
        m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
    }
}
複製代碼

Proxy0的父類是Proxy,它有一個構造方法,接受一個InvocationHandler類型的參數,保存爲了實例變量h,h定義在父類Proxy中,它實現了接口IService,對於每一個方法,如sayHello,它調用InvocationHandler的invoke方法,對於Object中的方法,如hashCode, equals和toString,Proxy0一樣轉發給了InvocationHandler。

能夠看出,這個類定義自己與被代理的對象沒有關係,與InvocationHandler的具體實現也沒有關係,而主要與接口數組有關,給定這個接口數組,它動態建立每一個接口的實現代碼,實現就是轉發給InvocationHandler,與被代理對象的關係以及對它的調用由InvocationHandler的實現管理。

咱們是怎麼知道$Proxy0的定義的呢?對於Oracle的JVM,能夠配置java的一個屬性獲得,好比:

java -Dsun.misc.ProxyGenerator.saveGeneratedFiles=true shuo.laoma.dynamic.c86.SimpleJDKDynamicProxyDemo
複製代碼

以上命令會把動態生成的代理類Proxy0保存到文件Proxy0.class中,經過一些反編譯器工具好比JD-GUI就能夠獲得源碼。

理解了代理類的定義,後面的代碼就比較容易理解了,就是獲取構造方法,建立代理對象。

動態代理的優勢

相比靜態代理,動態代理看起來麻煩了不少,它有什麼好處呢?使用它,能夠編寫通用的代理邏輯,用於各類類型的被代理對象,而不須要爲每一個被代理的類型都建立一個靜態代理類。看個簡單的示例:

public class GeneralProxyDemo {
    static interface IServiceA {
        public void sayHello();
    }

    static class ServiceAImpl implements IServiceA {

        @Override
        public void sayHello() {
            System.out.println("hello");
        }
    }

    static interface IServiceB {
        public void fly();
    }

    static class ServiceBImpl implements IServiceB {

        @Override
        public void fly() {
            System.out.println("flying");
        }
    }

    static class SimpleInvocationHandler implements InvocationHandler {
        private Object realObj;

        public SimpleInvocationHandler(Object realObj) {
            this.realObj = realObj;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("entering " + realObj.getClass().getSimpleName() + "::" + method.getName());
            Object result = method.invoke(realObj, args);
            System.out.println("leaving " + realObj.getClass().getSimpleName() + "::" + method.getName());
            return result;
        }
    }

    @SuppressWarnings("unchecked")
    private static <T> T getProxy(Class<T> intf, T realObj) {
        return (T) Proxy.newProxyInstance(intf.getClassLoader(), new Class<?>[] { intf },
                new SimpleInvocationHandler(realObj));
    }

    public static void main(String[] args) throws Exception {
        IServiceA a = new ServiceAImpl();
        IServiceA aProxy = getProxy(IServiceA.class, a);
        aProxy.sayHello();

        IServiceB b = new ServiceBImpl();
        IServiceB bProxy = getProxy(IServiceB.class, b);
        bProxy.fly();
    }
}
複製代碼

在這個例子中,有兩個接口IServiceA和IServiceB,它們對應的實現類是ServiceAImpl和ServiceBImpl,雖然它們的接口和實現不一樣,但利用動態代理,它們能夠調用一樣的方法getProxy獲取代理對象,共享一樣的代理邏輯SimpleInvocationHandler,即在每一個方法調用先後輸出一條跟蹤調試語句。程序輸出爲:

entering ServiceAImpl::sayHello
hello
leaving ServiceAImpl::sayHello
entering ServiceBImpl::fly
flying
leaving ServiceBImpl::fly
複製代碼

cglib動態代理

用法

Java SDK動態代理的侷限在於,它只能爲接口建立代理,返回的代理對象也只能轉換到某個接口類型,若是一個類沒有接口,或者但願代理非接口中定義的方法,那就沒有辦法了。有一個第三方的類庫[cglib(https://github.com/cglib/cglib)能夠作到這一點,Spring,Hibernate等都使用該類庫。咱們看個簡單的例子:

public class SimpleCGLibDemo {
    static class RealService {
        public void sayHello() {
            System.out.println("hello");
        }
    }

    static class SimpleInterceptor implements MethodInterceptor {

        @Override
        public Object intercept(Object object, Method method, Object[] args, MethodProxy proxy) throws Throwable {
            System.out.println("entering " + method.getName());
            Object result = proxy.invokeSuper(object, args);
            System.out.println("leaving " + method.getName());
            return result;
        }
    }

    @SuppressWarnings("unchecked")
    private static <T> T getProxy(Class<T> cls) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(cls);
        enhancer.setCallback(new SimpleInterceptor());
        return (T) enhancer.create();
    }

    public static void main(String[] args) throws Exception {
        RealService proxyService = getProxy(RealService.class);
        proxyService.sayHello();
    }
}
複製代碼

RealService表示被代理的類,它沒有接口。getProxy()爲一個類生成代理對象,這個代理對象能夠安全的轉換爲被代理類的類型,它使用了cglib的Enhancer類,Enhancer類的setSuperclass設置被代理的類,setCallback設置被代理類的public非final方法被調用時的處理類,Enhancer支持多種類型,這裏使用的類實現了MethodInterceptor接口,它與Java SDK中的InvocationHandler有點相似,方法名稱變成了intercept,多了一個MethodProxy類型的參數。

與前面的InvocationHandler不一樣,SimpleInterceptor中沒有被代理的對象,它經過MethodProxy的invokeSuper方法調用被代理類的方法:

Object result = proxy.invokeSuper(object, args);
複製代碼

注意,它不能這樣調用被代理類的方法:

Object result = method.invoke(object, args);    
複製代碼

object是代理對象,調用這個方法還會調用到SimpleInterceptor的intercept方法,形成死循環。

在main方法中,咱們也沒有建立被代理的對象,建立的對象直接就是代理對象。

基本實現原理

cglib的實現機制與Java SDK不一樣,它是經過繼承實現的,它也是動態建立了一個類,但這個類的父類是被代理的類,代理類重寫了父類的全部public非final方法,改成調用Callback中的相關方法,在上例中,調用SimpleInterceptor的intercept方法。

Java SDK代理與cglib代理比較

Java SDK代理面向的是一組接口,它爲這些接口動態建立了一個實現類,接口的具體實現邏輯是經過自定義的InvocationHandler實現的,這個實現是自定義的,也就是說,其背後都不必定有真正被代理的對象,也可能多個實際對象,根據狀況動態選擇。cglib代理面向的是一個具體的類,它動態建立了一個新類,繼承了該類,重寫了其方法。

從代理的角度看,Java SDK代理的是對象,須要先有一個實際對象,自定義的InvocationHandler引用該對象,而後建立一個代理類和代理對象,客戶端訪問的是代理對象,代理對象最後再調用實際對象的方法,cglib代理的是類,建立的對象只有一個。

若是目的都是爲一個類的方法加強功能,Java SDK要求該類必須有接口,且只能處理接口中的方法,cglib沒有這個限制。

動態代理的應用 - AOP

利用cglib動態代理,咱們實現一個極簡的AOP框架,演示AOP的基本思路和技術。

用法

咱們添加一個新的註解@Aspect,其定義爲:

@Retention(RUNTIME)
@Target(TYPE)
public @interface Aspect {
    Class<?>[] value();
}
複製代碼

它用於註解切面類,它有一個參數,能夠指定要加強的類,好比:

@Aspect({ServiceA.class,ServiceB.class})
public class ServiceLogAspect 複製代碼

ServiceLogAspect就是一個切面,它負責類ServiceA和ServiceB的日誌切面,即爲這兩個類增長日誌功能。

再好比:

@Aspect({ServiceB.class})
public class ExceptionAspect 複製代碼

ExceptionAspect也是一個切面,它負責類ServiceB的異常切面。

這些切面類與主體類怎麼協做呢?咱們約定,切面類能夠聲明三個方法before/after/exception,在主體類的方法調用前/調用後/出現異常時分別調用這三個方法,這三個方法的聲明需符合以下簽名:

public static void before(Object object, Method method, Object[] args) public static void after(Object object, Method method, Object[] args, Object result) public static void exception(Object object, Method method, Object[] args, Throwable e) 複製代碼

object, method和args與cglib MethodInterceptor中的invoke參數同樣,after中的result表示方法執行的結果,exception中的e表示發生的異常類型。

ServiceLogAspect實現了before和after方法,加了一些日誌,其代碼爲:

@Aspect({ ServiceA.class, ServiceB.class })
public class ServiceLogAspect {

    public static void before(Object object, Method method, Object[] args) {
        System.out.println("entering " + method.getDeclaringClass().getSimpleName()
                + "::" + method.getName() + ", args: " + Arrays.toString(args));
    }

    public static void after(Object object, Method method, Object[] args, Object result) {
        System.out.println("leaving " + method.getDeclaringClass().getSimpleName()
                + "::" + method.getName() + ", result: " + result);
    }
}
複製代碼

ExceptionAspect只實現exception方法,在異常發生時,輸出一些信息,代碼爲:

@Aspect({ ServiceB.class })
public class ExceptionAspect {
    public static void exception(Object object, Method method, Object[] args, Throwable e) {
        System.err.println("exception when calling: " + method.getName()
        + "," + Arrays.toString(args));
    }
}
複製代碼

ServiceLogAspect的目的是在類ServiceA和ServiceB全部方法的執行先後加一些日誌,而ExceptionAspect的目的是在類ServiceB的方法執行出現異常時收到通知並輸出一些信息。它們都沒有修改類ServiceA和ServiceB自己,自己作的事是比較通用的,與ServiceA和ServiceB的具體邏輯關係也不密切,但又想改變ServiceA/ServiceB的行爲,這就是AOP的思惟。

只是聲明一個切面類是不起做用的,咱們須要與上節介紹的DI容器結合起來,咱們實現一個新的容器CGLibContainer,它有一個方法:

public static <T> T getInstance(Class<T> cls) 複製代碼

經過該方法獲取ServiceA或ServiceB,它們的行爲就會被改變,ServiceA和ServiceB的定義與上節同樣,這裏重複下:

public class ServiceA {
    @SimpleInject
    ServiceB b;
    
    public void callB(){
        b.action();
    }
}

public class ServiceB {
    public void action(){
        System.out.println("I'm B");
    }
}
複製代碼

經過CGLibContainer獲取ServiceA,會自動應用ServiceLogAspect,好比:

ServiceA a = CGLibContainer.getInstance(ServiceA.class);
a.callB();
複製代碼

輸出爲:

entering ServiceA::callB, args: []
entering ServiceB::action, args: []
I'm B leaving ServiceB::action, result: null leaving ServiceA::callB, result: null 複製代碼

實現原理

這是怎麼作到的呢?CGLibContainer在初始化的時候,會分析帶有@Aspect註解的類,分析出每一個類的方法在調用前/調用後/出現異常時應該調用哪些方法,在建立該類的對象時,若是有須要被調用的方法,則建立一個動態代理對象,下面咱們具體來看下代碼。

爲簡化起見,咱們基於上節介紹的DI容器的第一個版本,即每次獲取對象時都建立一個,不支持單例。

咱們定義一個枚舉InterceptPoint,表示切點(調用前/調用後/出現異常):

public static enum InterceptPoint {
    BEFORE, AFTER, EXCEPTION
}
複製代碼

在CGLibContainer中定義一個靜態變量,表示每一個類的每一個切點的方法列表,定義以下:

static Map<Class<?>, Map<InterceptPoint, List<Method>>> interceptMethodsMap = new HashMap<>();
複製代碼

咱們在CGLibContainer的類初始化過程當中初始化該對象,方法是分析每一個帶有@Aspect註解的類,這些類通常能夠經過掃描全部的類獲得,爲簡化起見,咱們將它們寫在代碼中,以下所示:

static Class<?>[] aspects = new Class<?>[] { ServiceLogAspect.class, ExceptionAspect.class };
複製代碼

分析這些帶@Aspect註解的類,並初始化interceptMethodsMap的代碼以下所示:

static {
    init();
}

private static void init() {
    for (Class<?> cls : aspects) {
        Aspect aspect = cls.getAnnotation(Aspect.class);
        if (aspect != null) {
            Method before = getMethod(cls, "before", new Class<?>[] {
                Object.class, Method.class, Object[].class });
            Method after = getMethod(cls, "after",
                    new Class<?>[] {
                Object.class, Method.class, Object[].class, Object.class });
            Method exception = getMethod(cls, "exception",
                    new Class<?>[] {
                Object.class, Method.class, Object[].class, Throwable.class });
            Class<?>[] intercepttedArr = aspect.value();
            for (Class<?> interceptted : intercepttedArr) {
                addInterceptMethod(interceptted, InterceptPoint.BEFORE, before);
                addInterceptMethod(interceptted, InterceptPoint.AFTER, after);
                addInterceptMethod(interceptted, InterceptPoint.EXCEPTION, exception);
            }
        }
    }
}
複製代碼

對每一個切面,即帶有@Aspect註解的類cls,查找其before/after/exception方法,調用方法addInterceptMethod將其加入目標類的切點方法列表中,addInterceptMethod的代碼爲:

private static void addInterceptMethod(Class<?> cls, InterceptPoint point, Method method) {
    if (method == null) {
        return;
    }
    Map<InterceptPoint, List<Method>> map = interceptMethodsMap.get(cls);
    if (map == null) {
        map = new HashMap<>();
        interceptMethodsMap.put(cls, map);
    }
    List<Method> methods = map.get(point);
    if (methods == null) {
        methods = new ArrayList<>();
        map.put(point, methods);
    }
    methods.add(method);
}
複製代碼

準備好了每一個類的每一個切點的方法列表,咱們來看根據類型建立實例的代碼:

private static <T> T createInstance(Class<T> cls) throws InstantiationException, IllegalAccessException {
    if (!interceptMethodsMap.containsKey(cls)) {
        return (T) cls.newInstance();
    }
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(cls);
    enhancer.setCallback(new AspectInterceptor());
    return (T) enhancer.create();
}
複製代碼

若是類型cls不須要加強,則直接調用cls.newInstance(),不然使用cglib建立動態代理,callback爲AspectInterceptor,其代碼爲:

static class AspectInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object object, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        //執行before方法
        List<Method> beforeMethods = getInterceptMethods(
                object.getClass().getSuperclass(), InterceptPoint.BEFORE);
        for (Method m : beforeMethods) {
            m.invoke(null, new Object[] { object, method, args });
        }

        try {
            // 調用原始方法
            Object result = proxy.invokeSuper(object, args);

            // 執行after方法
            List<Method> afterMethods = getInterceptMethods(
                    object.getClass().getSuperclass(), InterceptPoint.AFTER);
            for (Method m : afterMethods) {
                m.invoke(null, new Object[] { object, method, args, result });
            }
            return result;
        } catch (Throwable e) {
            //執行exception方法
            List<Method> exceptionMethods = getInterceptMethods(
                    object.getClass().getSuperclass(), InterceptPoint.EXCEPTION);
            for (Method m : exceptionMethods) {
                m.invoke(null, new Object[] { object, method, args, e });
            }
            throw e;
        }
    }
}
複製代碼

這個代碼也容易理解,它根據原始類的實際類型查找應該執行的before/after/exception方法列表,在調用原始方法前執行before方法,執行後執行after方法,出現異常時執行exception方法,getInterceptMethods方法的代碼爲:

static List<Method> getInterceptMethods(Class<?> cls, InterceptPoint point) {
    Map<InterceptPoint, List<Method>> map = interceptMethodsMap.get(cls);
    if (map == null) {
        return Collections.emptyList();
    }
    List<Method> methods = map.get(point);
    if (methods == null) {
        return Collections.emptyList();
    }
    return methods;
}
複製代碼

這個代碼也容易理解。

CGLibContainer最終的getInstance方法就簡單了,它調用createInstance建立實例,代碼以下所示:

public static <T> T getInstance(Class<T> cls) {
    try {
        T obj = createInstance(cls);
        Field[] fields = cls.getDeclaredFields();
        for (Field f : fields) {
            if (f.isAnnotationPresent(SimpleInject.class)) {
                if (!f.isAccessible()) {
                    f.setAccessible(true);
                }
                Class<?> fieldCls = f.getType();
                f.set(obj, getInstance(fieldCls));
            }
        }
        return obj;
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}
複製代碼

完整的代碼能夠在github上獲取,文末有連接。這個AOP的實現是很是粗糙的,主要用於解釋動態代理的應用和AOP的一些基本思路和原理。

小結

本節探討了Java中的代理,從靜態代理到兩種動態代理,動態代理普遍應用於各類系統程序、框架和庫中,用於爲應用程序員提供易用的支持、實現AOP、以及其餘靈活通用的功能,理解了動態代理,咱們就能更好的利用這些系統程序、框架和庫,在須要的時候,也能夠本身建立動態代理。

下一節,咱們來進一步理解Java中的類加載過程,探討如何利用自定義的類加載器實現更爲動態強大的功能。

(與其餘章節同樣,本節全部代碼位於 github.com/swiftma/pro…,位於包shuo.laoma.dynamic.c86下)


未完待續,查看最新文章,敬請關注微信公衆號「老馬說編程」(掃描下方二維碼),從入門到高級,深刻淺出,老馬和你一塊兒探索Java編程及計算機技術的本質。用心原創,保留全部版權。

相關文章
相關標籤/搜索