代理那些事

1.無代理程序員

凡是都要由淺入深,學習也不例外。先來一個 Hello World 吧: 架構

public interface Hello {

    void say(String name);
}

這是一個 Hello 接口,不用解釋了,你們都懂的。趕忙上實現類吧:框架

public class HelloImpl implements Hello {

    @Override
    public void say(String name) {
        System.out.println("Hello! " + name);
    }
}

 可是若是要在 println() 方法前面和後面分別須要處理一些邏輯,怎麼作呢?把這些邏輯寫死在 say() 方法裏面嗎 ?這時就須要代理了ide

2.靜態代理模式函數

我要用代理!寫一個 HelloProxy 類,讓它去調用 HelloImpl 的 say() 方法,在調用的先後分別進行邏輯處理不就好了嗎?趕忙搞一個吧:工具

public class HelloProxy implements Hello {

    private HelloImpl helloImpl;

    public HelloProxy() {
        helloImpl = new HelloImpl();
    }

    @Override
    public void say(String name) {
        before();
        helloImpl.say(name);
        after();
    }

    private void before() {
        System.out.println("Before");
    }

    private void after() {
        System.out.println("After");
    }
}

我將 HelloProxy 類實現了 Hello 接口(和 HelloImpl 實現相同的接口),而且在構造方法中 new 出一個 HelloImpl 類的實例。這樣一來,我就能夠在 HelloProxy 的 say() 方法裏面去調用 HelloImpl 的 say() 方法了。更重要的是,我還能夠在調用的先後分別加上 before() 與 after() 方法,在這兩個方法裏去實現那些先後邏輯。學習

用一個 main 方法來測試一下吧:測試

public static void main(String[] args) {
    Hello helloProxy = new HelloProxy();
    helloProxy.say("Jack");
}

運行後,打印出:this

Before
Hello! Jack
After.net

3.動態代理模式

因而我就是用 JDK 給咱們提供的動態代理方案,寫了一個 DynamicProxy:

public class DynamicProxy implements InvocationHandler {

    private Object target;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object result = method.invoke(target, args);
        after();
        return result;
    }

    ...
}

在 DynamicProxy 類中,我定義了一個 Object 類型的 target 變量,它就是被代理的目標對象,經過構造函數來初始化(如今流行叫「注入」了,我以爲叫「射入」也不錯哦!構造函數初始化叫「正着射」,因此 reflect 方式就叫「反着射」,簡稱「反射」)。

言歸正傳,DynamicProxy 實現了 InvocationHandler 接口,那麼必須實現該接口的 invoke 方法,參數不作解釋,望文生義吧,是 JRE 給咱們「射」進來的。在該方法中,直接經過反射去 invoke method,在調用先後分別處理 before 與 after,最後將 result 返回。

寫一個 main() 方法看看實際怎麼用吧:

public static void main(String[] args) {
    Hello hello = new HelloImpl();

    DynamicProxy dynamicProxy = new DynamicProxy(hello);

    Hello helloProxy = (Hello) Proxy.newProxyInstance(
        hello.getClass().getClassLoader(),
        hello.getClass().getInterfaces(),
        dynamicProxy
    );

    helloProxy.say("Jack");
}

沒錯,意思就是,用我寫的這個通用的 DynamicProxy 類去包裝 HelloImpl 實例,而後再調用 JDK 給咱們提供的 Proxy 類的工廠方法 newProxyInstance() 去動態地建立一個 Hello 接口的代理類,最後調用這個代理類的 say() 方法。

運行一下,結果和之前同樣,動態代理成功了。其實,動態代理就是幫咱們自動生成 XxxProxy 類的法寶啊!

要注意的是,Proxy.newProxyInstance() 方法的參數實在是讓我「蛋碎一地」!

參數1:ClassLoader
參數2:該實現類的全部接口
參數3:動態代理對象

調用完了還要來一個強制類型轉換一下。

wocao!這一坨 shi 必定要想辦法封裝一下,避免再次發生處處都是 Proxy.newProxyInstance(),這樣架構師又要罵我了。因而我將這個 DynamicProxy 重構了:

public class DynamicProxy implements InvocationHandler {

    ...

    @SuppressWarnings("unchecked")
    public <T> T getProxy() {
        return (T) Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            this
        );
    }

    ...
}

我在 DynamicProxy 裏添加了一個 getProxy() 方法,無需傳入任何參數,將剛纔所說的那一坨 shi,放在這個方法中,而且該方法返回一個泛型類型,就不會強制類型轉換了。方法頭上加那個 @SuppressWarnings("unchecked") 註解表示忽略編譯時的警告(由於 Proxy.newProxyInstance() 方法返回的是一個 Object,這裏我強制轉換爲 T 了,這是向下轉型,IDE 中就會有警告,編譯時也會出現提示,很煩)。

好了,這下子使用 DynamicProxy 就簡單了吧:

public static void main(String[] args) {
    DynamicProxy dynamicProxy = new DynamicProxy(new HelloImpl());
    Hello helloProxy = dynamicProxy.getProxy();

    helloProxy.say("Jack");
}

確實簡單用 2 行代理就去掉了前面的 7 行代碼(省了 5 行),架構師看到了這樣的代碼確定會表揚我!

通過一番代碼重構後,我提交了全部的代碼,架構師看到了,沒有吱聲…… 可我總算學會了動態代理。

用了這個 DynamicProxy 之後,我以爲它仍是很是爽的,爽的地方是,接口變了,這個動態代理類不用動。而靜態代理就不同了,接口變了,實現類還要動,代理類也要動。但我也發現動 態代理並非「萬靈丹」,它也有搞不定的時候,好比說,我要代理一個沒有任何接口的類,它就沒有勇武之地了!這就是 JDK 給咱們提供的動態代理,讓我不知道該說什麼了。

因而我又開始調研,可否代理沒有接口的類呢?終於讓我找到了這顆「銀彈」!那就是 CGLib 這個類庫。雖然它看起來不太起眼,但 Spring、Hibernate 這樣牛逼的開源框架都用到了它。它就是一個在運行期間動態生成字節碼的工具,也就是動態生成代理類了。提及來好高深,實際用起來一點都不難。我再搞一個 CGLibProxy 吧:

4. CGLib 動態代理

public class CGLibProxy implements MethodInterceptor {

    public <T> T getProxy(Class<T> cls) {
        return (T) Enhancer.create(cls, this);
    }

    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        before();
        Object result = proxy.invokeSuper(obj, args);
        after();
        return result;
    }

    ...
}

須要實現 CGLib 給咱們提供的 MethodInterceptor 實現類,並填充 intercept() 方法。方法中最後一個 MethodProxy 類型的參數 proxy,值得注意!CGLib 給咱們提供的是方法級別的代理,也能夠理解爲對方法的攔截(這不就是傳說中的「方法攔截器」嗎?)。這個功能對於咱們這羣屌絲程序員而言,如同雪中送炭 啊,此乃神器也!咱們直接調用 proxy 的 invokeSuper() 方法,將被代理的對象 obj 以及方法參數 args 傳入其中便可。

與 DynamicProxy 相似,我在 CGlibProxy 中也添加了一個泛型的 getProxy() 方法,便於咱們能夠快速地獲取自動生成的代理對象。仍是用一個 main() 方法來描述吧:

public static void main(String[] args) {
    CGLibProxy cgLibProxy = new CGLibProxy();
    HelloImpl helloProxy = cgLibProxy.getProxy(HelloImpl.class);

    helloProxy.say("Jack");
}

仍然經過 2 行代碼就能夠返回代理對象了,與 JDK 動態代理不一樣的是,這裏不須要任何的接口信息,對誰均可以生成動態代理對象(無論它是「屌絲」仍是「高富帥」)。說它是神器,過度嗎?

我一貫都是以追求完美而著稱,2 行代碼返回代理對象,我以爲仍是有些多餘,我不想老是去 new 這個 CGLibProxy 對象,最好 new 一次,之後隨時拿隨時用。因而我想到了「單例模式」:

public class CGLibProxy implements MethodInterceptor {

    private static CGLibProxy instance = new CGLibProxy();
    
    private CGLibProxy() {
    }

    public static CGLibProxy getInstance() {
        return instance;
    }

    ...
}

我加了以上幾行代碼,就搞定了!須要說明的是:這裏有一個 private 的構造方法,就是爲了限制外界不能再去 new 它了,換句話說,我在這裏把它給「閹」了。

用一個 main() 方法來證實個人簡單主義思想:

public static void main(String[] args) {
    HelloImpl helloImpl = CGLibProxy.getInstance().getProxy(HelloImpl.class);

    helloImpl.say("Jack");
}

沒錯吧?只需 1 行代碼就能夠獲取代理對象了!

總結一下,咱們今天談到了無代理、靜態代理、JDK 動態代理、CGLib 動態代理。

相關文章
相關標籤/搜索