Java編程的邏輯 (86) - 動態代理


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


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

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

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

和注解類似,在大部分的應用編程中,我們不需要自己實現動態代理,而只需要按照框架和庫的文檔說明進行使用就可以了。不過,理解動態代理有助於我們更為深刻的理解這些框架和庫,也能更好的應用它們,在自己的業務需要時,也能自己實現。

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

靜態代理

我們首先來看代理,代理是一個比較通用的詞,作為一個軟件設計模式,它在《設計模式》一書中被提出,基本概念和日常生活中的概念是類似的,代理背后一般至少有一個實際對象,代理的外部功能和實際對象一般是一樣的,用戶與代理打交道,不直接接觸實際對象,雖然外部功能和實際對象一樣,但代理有它存在的價值,比如:

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

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

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(http://jd.benow.ca/)就可以得到源碼。

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

動態代理的優點

相比靜態代理,動態代理看起來麻煩了很多,它有什么好處呢?使用它,可以編寫通用的代理邏輯,用於各種類型的被代理對象,而不需要為每個被代理的類型都創建一個靜態代理類。看個簡單的示例:

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中的類加載過程,探討如何利用自定義的類加載器實現更為動態強大的功能。

(與其他章節一樣,本節所有代碼位於 https://github.com/swiftma/program-logic,位於包shuo.laoma.dynamic.c86下)

----------------

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM