理解Java動態代理(一)

Java中動態代理實現方式主要有兩種,一種是JDK官方提供的基于接口的動態代理,另一種是CGLib提供的基于類的的動態代理。在Spring Aop框架中,默認是是實現了接口的類使用JDK動態代理,沒有實現接口的類使用CGlib動態代理,也可以設置強制全部都使用CGlib。

JDK提供的基于接口的動態代理
//定義接口Animal
public interface Animal {
    void eat();
}

//定義類Human,實現接口Animal
public class Human implements Animal {
    public void eat() {
        System.out.println("吃肉");
    }
}

//實現InvocationHandler,動態代理的新增邏輯都寫在具體的InvocationHandler實現里。
//需要注意的一點是invoke方法中的proxy參數是動態代理產生的代理對象,而不是被代理的對象。在invoke方法里調用proxy.invoke(human,args)會無限遞歸,導致StackOverFlow。
//執行被代理對象的方法需要將被代理對象的引用傳到InvocationHandler中。
public class HumanInvocationHandler implements InvocationHandler {
    private Human human;
    public HumanInvocationHandler(Human human){
        this.human = human;
    }
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("把肉煮熟");
        return method.invoke(human,args);
    }
}

//Main方法
public class Main {
    public static void main(String[] args){
        Human human = new Human();
        HumanInvocationHandler humanInvocationHandler = new HumanInvocationHandler(human);

        Animal animal = (Animal) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), Human.class.getInterfaces(), humanInvocationHandler);
        System.out.println("類名: " + animal.getClass());
        System.out.println("父類名: " + animal.getClass().getSuperclass());
        for(Class c:animal.getClass().getInterfaces()){
            System.out.println("接口名:" + c);
        }
        animal.eat();

        System.out.println("類名: " +human.getClass());
        System.out.println("父類名: " + human.getClass().getSuperclass());
        for(Class c:human.getClass().getInterfaces()) {
            System.out.println("接口名:" + c);
        }
        human.eat();
    }
}
輸出結果
代理對象
類名: class com.sun.proxy.$Proxy0
父類名: class java.lang.reflect.Proxy
接口名:interface intefaces.Animal
把肉煮熟
吃肉

原始對象
類名: class intefaces.classes.Human
父類名: class java.lang.Object
接口名:interface intefaces.Animal
吃肉

從輸出結果可以看出來生成的代理對象的類型是com.sun.proxy.$Proxy0,這是運行時動態生成的類型,它的父類是java.lang.reflect.Proxy,并且實現了接口 intefaces.Animal。執行這個類實現的接口的方法時,會轉發到InvocationHandler里來處理。
java.lang.reflect.Proxy這個類是所有JDK動態代理生成的代理類的父類,這個設計決定了JDK動態代理只能實現基于接口的動態代理。對普通的類做代理的話,生成的代理類必然是這個類的子類,因為JAVA語言規范里規定了一個類只能有一個父類,這就和所有代理類的父類都是java.lang.reflect.Proxy相沖突了。JAVA官方選擇同一個類作為所有代理類的父類肯定是經過深思熟慮的,具體的原因以后有空了去了解一下,不過說不定以后JAVA就原生支持基于類的動態代理了。

CGlib提供的基于類的動態代理
//引入CGlib依賴
    <dependencies>
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.2.4</version>
        </dependency>
    </dependencies>
//實現InvocationHandler接口,此InvocationHandler非JDK中的InvocationHandler,是CGlib提供的接口,繼承了Callback接口
//Callback接口的子接口有多種,我們這里選用了和JDK中定義一樣的InvocationHandler來實現Callback
public class HumanInvocationHandlerCGlib implements InvocationHandler {
    private Human human;

    public HumanInvocationHandlerCGlib(Human human){
        this.human = human;
    }
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("把肉煮熟");
        return method.invoke(human,args);
    }
}

//main方法
public class Main {
    public static void main(String[] args){
        Human human = new Human();
        Enhancer enhancer = new Enhancer();
        HumanInvocationHandlerCGlib humanInvocationHandlerCGlib = new HumanInvocationHandlerCGlib(human);
        enhancer.setSuperclass(Human.class);
        enhancer.setCallback(humanInvocationHandlerCGlib);
        Human proxy = (Human) enhancer.create();

        System.out.println("類名: " + proxy.getClass());
        System.out.println("父類名: " + proxy.getClass().getSuperclass());
        for(Class c:proxy.getClass().getInterfaces()){
            System.out.println("接口名:" + c);
        }
        proxy.eat();

        System.out.println("類名: " +human.getClass());
        System.out.println("父類名: " + human.getClass().getSuperclass());
        for(Class c:human.getClass().getInterfaces()) {
            System.out.println("接口名:" + c);
        }
        human.eat();
    }
}
輸出結果
代理對象
類名: class intefaces.classes.Human$$EnhancerByCGLIB$$6fa4b36a
父類名: class intefaces.classes.Human
接口名:interface net.sf.cglib.proxy.Factory
把肉煮熟
吃肉

原始對象
類名: class intefaces.classes.Human
父類名: class java.lang.Object
接口名:interface intefaces.Animal
吃肉

我們可以看到代理對象的類名是intefaces.classes.Human$$EnhancerByCGLIB$$6fa4b36a,它的父類是intefaces.classes.Human,同時實現了CGlib提供的net.sf.cglib.proxy.Factory接口。使用CGlib時需要注意,CGlib無法對Final類生成代理類,無法對Final方法進行代理。因為CGlib使用的是創建子類來實現代理,JAVA的語言規范里子類對以上例子都是無能修改的,JDK的動態代理則沒有這些限制。

總結
  1. JDK基于實現接口實現動態代理,CGlib基于創建子類實現動態代理。
  2. JDK動態代理不能代理沒有繼承接口的類,CGlib可以。
  3. JDK可以代理Final類和Final方法,CGlib不可以
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 從三月份找實習到現在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發崗...
    時芥藍閱讀 42,366評論 11 349
  • title: Jdk動態代理原理解析 tags:代理 categories:筆記 date: 2017-06-14...
    行徑行閱讀 19,336評論 3 36
  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,765評論 18 399
  • 隨著《夏日將至》的熱播,無意聽到一首很喜歡歌,《那些花兒》。也許青春就是這樣,在曾經的那個小城里,我們...
    單戀一座城閱讀 386評論 0 0
  • XiaomiRouter自學之路(10-GitHub搭建環境(Openwrt)) 通過前面幾個章節的學習,U-bo...
    Creator_Ly閱讀 1,634評論 0 1