電腦程式的思維邏輯 (86) - 動態代理

来源:http://www.cnblogs.com/swiftma/archive/2017/05/18/6869790.html
-Advertisement-
Play Games

本節探討Java中的動態代理,介紹其用法和基本實現原理,利用它實現簡單的AOP框架 ...


前面兩節,我們介紹了反射註解,利用它們,可以編寫通用靈活的程式,本節,我們來探討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編程及電腦技術的本質。用心原創,保留所有版權。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1. 創建、初始化索引、統一搜索入口、搜索結果展現--內容、標題高亮、關鍵詞搜索 2. 高級搜索:高級搜索增加多入口查詢(精確查詢、模糊查詢、首碼查詢等),每頁顯示條數自定義、索引結果數據設置、選擇索引文檔類型等 3. 通過A系統調用B系統的Rest服務,生成相關的二維碼,可以直接用戶手機app 1 ...
  • 章節:其他 ((主:單詞)) 用來醒目地強調這個句子中哪個詞語作主語 sentence: 關鍵語句(關鍵句子可以用這個標記“sentence:”來羅列) what目標 key瓶頸 who 誰 log: 日誌 shopping: 購物清單(可以用這個標記來羅列你的購物清單,冒號後面跟著列出你準備待購的 ...
  • 簡單工廠模式 (Simple Factory) 又叫靜態工廠方法(Static Factory Method)模式。 簡單工廠模式通常是定義一個工廠類,這個類可以根據不同變數返回不同類的產品實例。 但是簡單工廠模式不屬於23種Gof設計模式之一。 優點 簡單工廠模式的工廠類是整個模式的關鍵。其中包含 ...
  • CSV模塊 1、CSV文件格式 要在文本文件中存儲數據,最簡單的方式是講數據作為一系列逗號分隔的值(CSV)寫入文件,這樣的文件成為CSV文件,如下: AKDT,Max TemperatureF,Mean TemperatureF,Min TemperatureF,Max Dew PointF,Me ...
  • 緩存 Laravel 給多種緩存系統提供豐富而統一的 API,緩存配置信息位於 config/cache.php,在這個文件中你可以為你的應用程式指定預設的緩存驅動,Laravel 支持當前流行的緩存系統,如非常棒的 Memcached 和 Redis 。 Memcached 1、配置 使用 Mem ...
  • 1.java概述 1. 前言 1.1 學習方法 1.2 推薦博客 當代程式員都應該養成寫博客、看博客的習慣 1.3 博客編輯神器 2. 內容:Java概述 2.1 Java語言發展史 2.1.1 電腦語言發展史 閱讀電腦語言之後回答幾個問題: 2.1.2 Java語言發展史 閱讀java語言之後 ...
  • 200 OK 請求成功。一般用於GET與POST請求 301 Moved Permanently 永久移動。請求的資源已被永久的移動到新URI,返回信息會包括新的URI,瀏覽器會自動定向到新URI。今後任何新的請求都應使用新的URI代替 302 Found 臨時移動。與301類似。但資源只是臨時被移 ...
  • 分享一個VBA的一個把一個sheet中的多個table(每一個table又hyperlinks),分配在不同的sheet中的方法,做這個真的也是耗費了不少的腦細胞。 Option Explicit ’這個是一個好習慣 ’第一種方法,通過currentregion來判斷區域,但是不是很保險 Sub G ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...