Java 源碼剖析(08)--如何實現動態代理


動態代理是程序在運行期間動態構建代理對象和動態調用代理方法的一種機制,如何實現動態代理?JDK Proxy 和 CGLib 有什麼區別?

1)動態代理是如何實現的?

動態代理的常用實現方式是反射。反射機制是指程序在運行期間可以訪問、檢測和修改其本身狀態或行爲的一種能力,使用反射我們可以調用任意一個類對象,以及類對象中包含的屬性及方法。
但動態代理不止有反射一種實現方式,例如,動態代理可以通過 CGLib 來實現,而 CGLib 是基於 ASM(一個 Java 字節碼操作框架)而非反射實現的。簡單來說,動態代理是一種行爲方式,而反射或 ASM 只是它的一種實現手段而已。

JDK Proxy 和 CGLib 的區別主要體現在以下幾個方面:

  • DK Proxy 是 Java 語言自帶的功能,無需通過加載第三方類實現;
  • Java 對 JDK Proxy 提供了穩定的支持,並且會持續的升級和更新 JDK Proxy,例如 Java 8 版本中的 JDK
    Proxy 性能相比於之前版本提升了很多;
  • JDK Proxy 是通過攔截器加反射的方式實現的;
  • JDK Proxy 只能代理繼承接口的類;
  • JDK Proxy 實現和調用起來比較簡單;
  • CGLib 是第三方提供的工具,基於 ASM 實現的,性能比較高;
  • CGLib 無需通過接口來實現,它是通過實現子類的方式來完成調用的。

2)JDK Proxy 和 CGLib 的使用及代碼分析

2.1)JDK Proxy 動態代理實現

JDK Proxy 動態代理的實現無需引用第三方類,只需要實現 InvocationHandler 接口,重寫 invoke() 方法即可,整個實現代碼如下所示:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * JDK Proxy 相關示例
 */
public class ProxyExample {
    static interface Car {
        void running();
    }

    static class Bus implements Car {
        @Override
        public void running() {
            System.out.println("The bus is running.");
        }
    }

    static class Taxi implements Car {
        @Override
        public void running() {
            System.out.println("The taxi is running.");
        }
    }

    /**
     * JDK Proxy
     */
    static class JDKProxy implements InvocationHandler {
        private Object target; // 代理對象

        // 獲取到代理對象
        public Object getInstance(Object target) {
            this.target = target;
            // 取得代理對象
            return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                    target.getClass().getInterfaces(), this);
        }

        /**
         * 執行代理方法
         * @param proxy  代理對象
         * @param method 代理方法
         * @param args   方法的參數
         * @return
         * @throws InvocationTargetException
         * @throws IllegalAccessException
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args)
                throws InvocationTargetException, IllegalAccessException {
            System.out.println("動態代理之前的業務處理.");
            Object result = method.invoke(target, args); // 執行調用方法(此方法執行前後,可以進行相關業務處理)
            return result;
        }
    }

    public static void main(String[] args) {
        // 執行 JDK Proxy
        JDKProxy jdkProxy = new JDKProxy();
        Car carInstance = (Car) jdkProxy.getInstance(new Taxi());
        carInstance.running();

以上程序的執行結果是:

動態代理之前的業務處理.
The taxi is running.

可以看出 JDK Proxy 實現動態代理的核心是實現 Invocation 接口,我們查看 Invocation 的源碼,會發現裏面其實只有一個 invoke() 方法,源碼如下:

public interface InvocationHandler {
  public Object invoke(Object proxy, Method method, Object[] args)
          throws Throwable;
}

這是因爲在動態代理中有一個重要的角色也就是代理器,它用於統一管理被代理的對象,顯然 InvocationHandler 就是這個代理器,而 invoke() 方法則是觸發代理的執行方法,我們通過實現 Invocation 接口來擁有動態代理的能力。

2.2)CGLib 的實現

在使用 CGLib 之前,我們要先在項目中引入 CGLib 框架,在 pom.xml 中添加如下配置:

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

CGLib 實現代碼如下:

package com.lagou.interview;

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

import java.lang.reflect.Method;

public class CGLibExample {

    static class Car {
        public void running() {
            System.out.println("The car is running.");
        }
    }

    /**
     * CGLib 代理類
     */
    static class CGLibProxy implements MethodInterceptor {
        private Object target; // 代理對象

        public Object getInstance(Object target) {
            this.target = target;
            Enhancer enhancer = new Enhancer();
            // 設置父類爲實例類
            enhancer.setSuperclass(this.target.getClass());
            // 回調方法
            enhancer.setCallback(this);
            // 創建代理對象
            return enhancer.create();
        }

        @Override
        public Object intercept(Object o, Method method,
                                Object[] objects, MethodProxy methodProxy) throws Throwable {
            System.out.println("方法調用前業務處理.");
            Object result = methodProxy.invokeSuper(o, objects); // 執行方法調用
            return result;
        }
    }

    // 執行 CGLib 的方法調用
    public static void main(String[] args) {
        // 創建 CGLib 代理類
        CGLibProxy proxy = new CGLibProxy();
        // 初始化代理對象
        Car car = (Car) proxy.getInstance(new Car());
        // 執行方法
        car.running();

以上程序的執行結果是:

方法調用前業務處理.
The car is running.

可以看出 CGLib 和 JDK Proxy 的實現代碼比較類似,都是通過實現代理器的接口,再調用某一個方法完成動態代理的,唯一不同的是,CGLib 在初始化被代理類時,是通過 Enhancer 對象把代理對象設置爲被代理類的子類來實現動態代理的。因此被代理類不能被關鍵字 final 修飾,如果被 final 修飾,再使用 Enhancer 設置父類時會報錯,動態代理的構建會失敗。

2.2)Lombok 原理分析

Lombok,它屬於 Java 的一個熱門工具類,使用它可以有效的解決代碼工程中那些繁瑣又重複的代碼,如 Setter、Getter、toString、equals 和 hashCode 等等,向這種方法都可以使用 Lombok 註解來完成。

例如,我們使用比較多的 Setter 和 Getter 方法,在沒有使用 Lombok 之前,代碼是這樣的:

public class Person {
    private Integer id;
    private String name;
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

在使用 Lombok 之後,代碼是這樣的:

@Data
public class Person {
    private Integer id;
    private String name;
}

可以看出 Lombok 讓代碼簡單和優雅了很多。

如果在項目中使用了 Lombok 的 Getter 和 Setter 註解,那麼想要在編碼階段成功調用對象的 set 或 get 方法,我們需要在 IDE 中安裝 Lombok 插件纔行,比如 Idea 的插件如下圖所示:
在這裏插入圖片描述

Lombok 的原理
Lombok 的實現和反射沒有任何關係,前面我們說了反射是程序在運行期的一種自省(introspect)能力,而 Lombok 的實現是在編譯期就完成了,爲什麼這麼說呢?
回到我們剛纔 Setter/Getter 的方法,當我們打開 Person 的編譯類就會發現,使用了 Lombok 的 @Data 註解後的源碼竟然是這樣的:
在這裏插入圖片描述
可以看出 Lombok 是在編譯期就爲我們生成了對應的字節碼。

其實 Lombok 是基於 Java 1.6 實現的 JSR 269: Pluggable Annotation Processing API 來實現的,也就是通過編譯期自定義註解處理器來實現的,它的執行步驟如下:
在這裏插入圖片描述
從流程圖中可以看出,在編譯期階段,當 Java 源碼被抽象成語法樹(AST)之後,Lombok 會根據自己的註解處理器動態修改 AST,增加新的代碼(節點),在這一切執行之後就生成了最終的字節碼(.class)文件,這就是 Lombok 的執行原理。

3)動態代理和靜態代理的區別?

靜態代理其實就是事先寫好代理類,可以手工編寫也可以使用工具生成,但它的缺點是每個業務類都要對應一個代理類,特別不靈活也不方便,於是就有了動態代理。
動態代理的常見使用場景有 RPC 框架的封裝、AOP(面向切面編程)的實現、JDBC 的連接等。

Spring 框架中同時使用了兩種動態代理 JDK Proxy 和 CGLib,當 Bean 實現了接口時,Spring 就會使用 JDK Proxy,在沒有實現接口時就會使用 CGLib,我們也可以在配置中指定強制使用 CGLib,只需要在 Spring 配置中添加 <aop:aspectj-autoproxy proxy-target-class=“true”/> 即可。

4)小結

本文介紹了 JDK Proxy 和 CGLib 的區別,JDK Proxy 是 Java 語言內置的動態代理,必須要通過實現接口的方式來代理相關的類,而 CGLib 是第三方提供的基於 ASM 的高效動態代理類,它通過實現被代理類的子類來實現動態代理的功能,因此被代理的類不能使用 final 修飾。

除了 JDK Proxy 和 CGLib 之外,還講了 Java 中常用的工具類 Lombok 的實現原理,它其實和反射是沒有任何關係的;最後講了動態代理的使用場景以及 Spring 中動態代理的實現方式。
——————————————————————————————————————————————
關注公衆號,回覆 【算法】,獲取高清算法書!
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章