代理模式- 靜態(tài)代理/動態(tài)代理

這一期來聊一聊代理模式:

在代理模式(Proxy Pattern)中,一個類代表另一個類的功能。這種類型的設計模式屬于結(jié)構(gòu)型模式。
代理這個詞 很容易想到最直接的含義 就是A代替B執(zhí)行B的內(nèi)容/A是B的代理人。

我們創(chuàng)建一個接口hero 定義一個方法 attack。這意味它的實現(xiàn)類HeroA要具體實現(xiàn)這個方法。正常的邏輯下這就結(jié)束了。
但是這時候我們不想把HeroA直接暴露出去,我們想讓一個代理類執(zhí)行HeroA的方法。 所以我們再來一個HeroProxy ,
它內(nèi)置了HeroA對象 并初始化一個實例調(diào)用Hero的方法。 這樣調(diào)用HeroProxy.attack()就達到了代理的目的。

(注意如果代理類的方法名稱是和被代理對象的方法相同的話,比如下面代碼中都是attack()方法,那么你需要讓它也實現(xiàn)方法對應的接口。)

public interface Hero {
    void attack();
}
--------------------
public class HeroA implements Hero {
    @Override
    public void attack(){
        System.out.println("hero a ! attack!");
    }
}
--------------------
public class HeroProxy implements Hero {
    private HeroA heroA;

    @Override
    public void attack(){
        System.out.println("hero's proxy is execute!");
        heroA=new HeroA();
        heroA.attack(); //  $$$
    }
}

ok 這是代理模式的基礎 也是最簡單的范例,在這個基礎上 有兩個比較重要的拓展:

靜態(tài)代理

和上一個例子性質(zhì)是相同的,可能你發(fā)現(xiàn)了:
1 在$$$標注方法的前后 我們可以手動加一些其他的 作為代理類的增強。但是手動加是比較麻煩的
2 接口的方法一旦改動了 那么代理類和被代理類都要改動 這是非常麻煩的 在實際開發(fā)中

動態(tài)代理

(動態(tài)代理簡直是天才的設計!我個人覺得)

JDK 動態(tài)代理機制

InvocationHandler 接口 和 Proxy 類 是這個機制的核心。

在Proxy 類中使用頻率最高的方法是:newProxyInstance() ,這個方法主要用來生成一個代理對象
在參數(shù)里面 會把被代理類的實現(xiàn)的接口傳進來,(比如上一個例子中 Hero就是代理對象實現(xiàn)的接口)

要實現(xiàn)動態(tài)代理的話,還必須需要實現(xiàn)InvocationHandler 來自定義處理邏輯。 當我們的動態(tài)代理對象調(diào)用一個方法時,這個方法的調(diào)用就會被轉(zhuǎn)發(fā)到實現(xiàn)InvocationHandler 接口類的 invoke 方法來調(diào)用。

invoke() 方法有下面三個參數(shù):
proxy :動態(tài)生成的代理類
method : 與代理類對象調(diào)用的方法相對應
args : 當前 method 方法的參數(shù)

也就是說:你通過Proxy 類的 newProxyInstance() 創(chuàng)建的代理對象在調(diào)用方法的時候,實際會調(diào)用到實現(xiàn)InvocationHandler 接口的類的 invoke()方法。 你可以在 invoke() 方法中自定義處理邏輯,比如在方法執(zhí)行前后做什么事情。

(了解java的朋友看到 這幾個詞匯 invoke InvocationHandler 、貌似眼熟 腦子里瞬間就會出現(xiàn)“反射”的影子,沒錯的它的核心機制就是依靠反射實現(xiàn)的)
(上面的文字描述可能你會很暈 暈是正常的 下面具體來演示)
還是上面那個例子:
首先我們實現(xiàn)了Hero接口 和 HeroA的接口實現(xiàn) 里面有attack()方法。 這時候我們想創(chuàng)建一個代理類。 跟上面代碼一模一樣.
為了方便。 為了更好的理解代碼 我們來從后往前倒著看。

JdkProxyFactory.getProxy(new HeroA()); 這個工廠類直接幫我們實現(xiàn)了HeroA()的代理類。我們不需再手動創(chuàng)建代理類了

被代理類作為一個參數(shù)傳了進去。那么它是怎么實現(xiàn)的呢

public class ExecuteDemo {
    public static void main(String[] args) {
        //jdk動態(tài)代理
        Hero heroproxy = (Hero)JdkProxyFactory.getProxy(new HeroA());
        heroproxy.attack();
        
        //動態(tài)代理
       HeroProxy heroProxy =new HeroProxy(hero);
       heroProxy.attack();

    }
}

倒著看代碼 發(fā)現(xiàn) 這個代理類 是被Proxy.newProxyInstance()創(chuàng)建出來的。它的三個參數(shù)都與你傳入的HeroA有關。

public class JdkProxyFactory {
    public static Object getProxy(Object target) {
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(), // 目標類的類加載
                target.getClass().getInterfaces(),  // 代理需要實現(xiàn)的接口,可指定多個
                new HeroInvocationHandler(target) // 代理對象對應的自定義 InvocationHandler
        );
    }
}

前兩個都是反射獲取類的信息 第三個參數(shù)是一個方法的返回值 這個方法內(nèi)部是什么樣的呢

public class HeroInvocationHandler implements  InvocationHandler{
    private final Object target;

    public HeroInvocationHandler(Object target){
        this.target=target;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
        System.out.println("before method " + method.getName());
        Object result = method.invoke(target, args);
        System.out.println("after method " + method.getName());
        return result;
}
}

這里的核心方法就是 public Object invoke。它重寫了反射里面invoke方法。傳入methood,參數(shù),Object對象
在里面你就可以自定義很多事情了 在調(diào)用方法前后 加一些自己你想加的東西 這就實現(xiàn)了代理功能。
“動態(tài)“的精髓就體現(xiàn)在 參數(shù)上面 你可以傳任意object 不需要手動的去創(chuàng)建代理類。

CGLIB 動態(tài)代理類

JDK 動態(tài)代理有一個最致命的問題是其只能代理實現(xiàn)了接口的類。
就是說上一個例子heroA如果沒實現(xiàn)Hero,那么jdk動態(tài)代理就失效了。
為了解決這個問題,我們可以用 CGLIB 動態(tài)代理機制來避免。

CGLIB(Code Generation Library)是一個基于ASM的字節(jié)碼生成庫,它允許我們在運行時對字節(jié)碼進行修改和動態(tài)生成。CGLIB 通過繼承方式實現(xiàn)代理。很多知名的開源框架都使用到了CGLIB, 例如 Spring 中的 AOP 模塊中:如果目標對象實現(xiàn)了接口,則默認采用 JDK 動態(tài)代理,否則采用 CGLIB 動態(tài)代理。

在 CGLIB 動態(tài)代理機制中 MethodInterceptor 接口和 Enhancer 類是核心。(一個接口 一個代理創(chuàng)建類 跟jdk動態(tài)代理有幾分神似對吧)
你需要自定義 MethodInterceptor 并重寫 intercept 方法,intercept 用于攔截增強被代理類的方法。

public interface MethodInterceptor
extends Callback{
    // 攔截被代理類中的方法
    public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,
                               MethodProxy proxy) throws Throwable;
}

obj :被代理的對象(需要增強的對象) heroA
method :被攔截的方法(需要增強的方法) attcak()
args :方法入?yún)?br> proxy :用于調(diào)用原始方法

Enhancer類和jdk動態(tài)里面的proxy類似 不過后者是工廠模式創(chuàng)建的

public class CglibProxyFactory {

    public static Object getProxy(Class<?> clazz) {
        // 創(chuàng)建動態(tài)代理增強類
        Enhancer enhancer = new Enhancer();
        // 設置類加載器
        enhancer.setClassLoader(clazz.getClassLoader());
        // 設置被代理類
        enhancer.setSuperclass(clazz);
        // 設置方法攔截器
        enhancer.setCallback(new DebugMethodInterceptor());
        // 創(chuàng)建代理類
        return enhancer.create();
    }
}

注意:CGLIB 動態(tài)代理是通過生成一個被代理類的子類來攔截被代理類的方法調(diào)用,因此不能代理聲明為 final 類型的類和方法。

如果你理解了這兩種機制,理解AOP 就是水到渠成了!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容