1. 程式人生 > >動態代理:如何深入理解和分析,不如手寫一個(原始碼包分析、樓主親測)

動態代理:如何深入理解和分析,不如手寫一個(原始碼包分析、樓主親測)

如何分類Java語言?

Java是靜態的強型別語言,但是因為提供了類似反射等機制,也具備了部分動態語言的能力。

一、動態代理的簡單描述

動態代理是一種方便執行時動態構建代理、動態處理代理方法呼叫的機制,很多場景都是利用類似的機制做到的,比如用來包裝RPC呼叫、面向切面的程式設計(AOP)。

實現動態代理的方式也有很多種,比如JDK自身提供的動態代理,也就是主要利用JDK的反射機制。還有一些更高效能的位元組碼操作機制,類似ASM、cglib(基於ASM)、Javassist等。

如果熟悉設計模式中的代理模式,代理可以看作是對呼叫目標的一個包裝,這樣我們對目的碼的呼叫不是直接發生的,而是通過代理完成的。

通過代理可以讓呼叫者與實現者之間解耦合。比如要進行RPC呼叫,框架內部的定址、序列化、反序列化,對於呼叫者往往是沒有太大的意義的,通過代理,可以提供更加友善的介面。

二、舉個栗子:Jdk Proxy實現代理

1、JDK Proxy原理

代理還是在基於反射的原理上實現的。

代理類Proxy呼叫處理器介面InvocationHandler,都位於java.lang.reflect反射包中。

這裡寫圖片描述

2、程式碼實現

package com.newframe.controllers.api;

import java.lang.reflect.InvocationHandler;
import
java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * @author:wangdong * @description:寫一個基於動態代理的栗子 */ public class TestMyDynamicProxy { public static void main(String[] args) { HelloImpl hello = new HelloImpl(); //獲取我自己實現的帶有特殊功能的MyInvocationHandler //為呼叫目標Hello建立代理物件
//進而程式就可以使用代理物件間接執行呼叫目標的邏輯 MyInvocationHandler handler = new MyInvocationHandler(hello); //構造動態代理程式碼例項 Hello proxyHello = (Hello) Proxy.newProxyInstance(HelloImpl.class.getClassLoader(),HelloImpl.class.getInterfaces(),handler); //呼叫代理方法 proxyHello.sayHello(); } } /** * 定一個Hello的介面 */ interface Hello{ void sayHello(); } /** * 實現這個Hello的介面 */ class HelloImpl implements Hello{ @Override public void sayHello() { System.out.println("Hello World"); } } /** * 定一個我的執行類,呼叫處理器 * 1、做一個動態代理,首先得實現一個InvocationHandler */ class MyInvocationHandler implements InvocationHandler{ //定一個成員變數 private Object target; //定義一個有參構造 public MyInvocationHandler(Object target) { this.target = target; } //2、重寫執行的方法 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("Invoking sayHello"); Object result = method.invoke(target,args); return result; } }

上述JDK Proxy的例子,非常簡單地實現了動態代理的構建和代理操作。

3、侷限性

從API的設計和實現的角度,上述基於JDK Proxy的動態代理,有他的侷限性。因為它是以介面為中心的,相當於添加了一種對於呼叫者沒有太大意義的限制。例項化的是Proxy物件,而不是真正的被呼叫物件,這在實踐中可能會帶來各種不便和能力的退化。

如果使用的是Cglib的方式實現動態代理,我們對介面的依賴就會被克服了。

三、Cglib動態代理的分析

1、基於cglib框架的優勢

  • Jdk Proxy。有的時候呼叫目標可能不便於實現額外的介面,從某種角度看,限定呼叫者必須實現介面,才可以使用動態代理是有些侵入性的。
    類似於cglib動態代理就沒有這種限制。

  • 只操作我們關心的類,而不必為其他相關類增加工作量。

  • 高效能
    如果從效能角度講。有人曾經得出結論說JDK Proxy比Cglib或者Javassist慢幾十倍。坦白說,經過Jdk自身的不斷髮展,在典型的場景下可以提供對等的效能水平,數量級別的巨大差距基本上是廣泛不存在的。
    而且反射機制效能在現代JDK中,自身已經得到了極大的改進和優化。同時JDK很多功能並不完全是反射,同樣使用了ASM進行位元組碼操作。

2、認識分析Spring框架的Cglib包

這個是Spring框架自身有的。這個也可以,但是我們經常用的還是真正的cglib包,見下一步

位於這個包下package org.springframework.cglib.proxy,比較常用的幾個類:Enhancer、InvocationHandler、MethodInterceptor

這裡寫圖片描述

關於這個Enhancer可以理解為一個類增強器,他和Jdk Proxy不同的是它允許為非介面的類建立一個動態代理的物件。
它主要有兩種回撥的方式:InvocationHandlerMethodInterceptor。
官方文件API地址:Enhancer

這裡寫圖片描述

3、認識真正的Cglib包

如果你是Maven專案,你需要匯入一個Cglib包。

<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.2.8</version>
</dependency>

看一下真正的Cglib包:
同樣,和Spring框架中的cglib包一樣。有常用的類和介面。
關於這個Enhancer可以理解為一個類增強器,他和Jdk Proxy不同的是它允許為非介面的類建立一個動態代理的物件。
它主要有兩種回撥的方式:InvocationHandlerMethodInterceptor。

這裡寫圖片描述

四、cglib基於MethodInterceptor的實現

1、實現程式碼:

package com.newframe.controllers.api;

/**
 * @author:wangdong
 * @description:基於cglib動態代理的例項
 * 當然你可以將不同的寫在不同的包中,我這裡為了方便就都寫在了一個裡面
 */

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * 定義一個Persion類
 */
class Persion{

    //定義幾個方法
    public void eat(){
        System.out.println("我叫小明,我在吃飯呢!");
    }

    public void study(){
        System.out.println("我是小明,我正在學習呢,請不要打擾我");
    }

}

/**
 * 定一個我的攔截器實現方法攔截器MethodInterceptor
 */
class MyInterceptor implements MethodInterceptor{

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {

        System.out.println("動態代理開始,我現在正要開始進行方法攔截了!");
        //Note: 此處一定要使用proxy的invokeSuper方法來呼叫目標類的方法
        //methodProxy.invoke(o,objects);報錯:Exception in thread "main" java.lang.StackOverflowError
        methodProxy.invokeSuper(o,objects);
        System.out.println("動態代理結束,我已經完成方法攔截了!");
        return null;
    }
}



public class CglibInterceptorTest {
    public static void main(String[] args) {
        //例項化一個增強器
        Enhancer enhancer = new Enhancer();
        //設定目標類
        enhancer.setSuperclass(Persion.class);
        //設定攔截物件
        enhancer.setCallback(new MyInterceptor());
        //生成代理類並返回一個例項
        Persion persion = (Persion) enhancer.create();
        persion.eat();
        persion.study();
    }
}

2、輸出結果

動態代理開始,我現在正要開始進行方法攔截了!
我叫小明,我在吃飯呢!
動態代理結束,我已經完成方法攔截了!
動態代理開始,我現在正要開始進行方法攔截了!
我是小明,我正在學習呢,請不要打擾我
動態代理結束,我已經完成方法攔截了!

五、cglib基於InvocationHandler的實現

這個和JDK Proxy不同的是,它既可以動態代理處理純類,也可以處理實現了介面的類。

1、實現程式碼:

package com.newframe.controllers.api;


import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.InvocationHandler;
import net.sf.cglib.proxy.Proxy;

import java.lang.reflect.Method;

/**
 * 定義一個介面類
 */
interface Animal{

    void run();
}

/**
 * 定義一個狗類,實現了Animal介面
 */
class Dog implements Animal{

    @Override
    public void run(){
        System.out.println("我是一隻狗,我正在快樂的奔跑呢!");
    }

}

/**
 * 定義一個貓類
 */
class Cat {

    public void eat(){
        System.out.println("我是一隻貓,我正在玩遊戲!");
    }
}

class MyHandler implements InvocationHandler{

    private Object target;

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

    @Override
    public Object invoke(Object o, Method method, Object[] objects) throws Throwable {

        //在這邊可以對一個類中的多個方法,做不同的操作
        //符合Aop面向切面程式設計的思想
        String m = method.getName();
        System.out.println(m + "方法執行開始");
        System.out.println("給" + m + "方法加個日誌");
        System.out.println("給" + m + "方法加個攔截器");
        Object result = method.invoke(target,objects);

        return result;
    }
}

/**
 * @author:wangdong
 * @description:cglib基於InvocationHandler的實現
 */
public class CglibInvocationHandlerTest {
    public static void main(String[] args) {

        //動態代理純Class類
        Cat cat = new Cat();
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Cat.class);
        //這個是關鍵,需要載入類
        enhancer.setClassLoader(CglibInvocationHandlerTest.class.getClassLoader());

        MyHandler myHandler = new MyHandler(cat);
        enhancer.setCallback(myHandler);

        Cat c = (Cat) enhancer.create();
        c.eat();

        //動態代理處理,實現了介面的實現類
        Dog dog = new Dog();
        Enhancer en = new Enhancer();
        en.setSuperclass(Dog.class);
        en.setClassLoader(CglibInvocationHandlerTest.class.getClassLoader());
        MyHandler handler = new MyHandler(dog);
        en.setCallback(handler);

        Dog d = (Dog) en.create();
        d.run();
    }
}

2、輸出結果

我是一隻貓,我正在玩遊戲!
我是一隻狗,我正在快樂的奔跑呢!

六、結語

相信通過自己寫一次程式碼,對整個動態代理應該有了非常清晰的認識了吧。
凡事光看是不足夠的,自己動手一次,各種查詢資料。能夠有非常深入的思考。

最後附一個知乎大神討論較多的關於動態代理的帖子:
動態代理