Java 靜態代理、Java動態代理、CGLIB動態代理

開篇java

 

Java 的代理就是客戶類再也不直接和委託類打交道, 而是經過一箇中間層來訪問, 這個中間層就是代理。爲啥要這樣呢, 是由於使用代理有 2 個優點:數據庫

  • 能夠隱藏委託類的實現安全

  • 能夠實現客戶與委託類之間的解耦, 在不修改委託類代碼的狀況下可以作一些額外的處理網絡

 

咱們舉個很常見的例子: 工廠會生產不少的玩具, 可是咱們買玩具都是到商店買的, 而不是到工廠去買的, 工廠怎麼生產咱們並不關心, 咱們只知道到商店能夠買到本身想要的玩具,而且,若是咱們須要送人的話商店能夠把這些玩具使用禮品盒包裝。這個工廠就是委託類, 商店就是代理類, 咱們就是客戶類。數據結構

在 Java 中咱們有不少場景須要使用代理類, 好比遠程 RPC 調用的時候咱們就是經過代理類去實現的, 還有 Spring 的 AOP 切面中咱們也是爲切面生成了一個代理類等等。
代理類主要分爲靜態代理、JDK 動態代理和 CGLIB 動態代理,它們各有優缺點,沒有最好的, 存在就是有意義的,在不一樣的場景下它們會有不一樣的用武之地。併發

1. Java 靜態代理

首先, 定義接口和接口的實現類, 而後定義接口的代理對象, 將接口的實例注入到代理對象中, 而後經過代理對象去調用真正的實現類,實現過程很是簡單也比較容易理解, 靜態代理的代理關係在編譯期間就已經肯定了的。它適合於代理類較少且肯定的狀況。它可實如今怒修改委託類代碼的狀況下作一些額外的處理,好比包裝禮盒,實現客戶類與委託類的解耦。缺點是隻適用委託方法少的狀況下, 試想一下若是委託類有幾百上千個方法, 豈不是很難受, 要在代理類中寫一堆的代理方法。這個需求動態代理能夠搞定框架

// 委託接口
public interface IHelloService {

    /**
     * 定義接口方法
     * @param userName
     * @return
     */
    String sayHello(String userName);

}
// 委託類實現
public class HelloService implements IHelloService {

    @Override
    public String sayHello(String userName) {
        System.out.println("helloService" + userName);
        return "HelloService" + userName;
    }
}

// 代理類
public class StaticProxyHello implements IHelloService {

    private IHelloService helloService = new HelloService();

    @Override
    public String sayHello(String userName) {
        /** 代理對象能夠在此處包裝一下*/
        System.out.println("代理對象包裝禮盒...");
        return helloService.sayHello(userName);
    }
}
// 測試靜態代理類
public class MainStatic {
    public static void main(String[] args) {
        StaticProxyHello staticProxyHello = new StaticProxyHello();
        staticProxyHello.sayHello("isole");
    }
}

2. 動態代理技術

代理類在程序運行時建立的代理方式被成爲 動態代理。在瞭解動態代理以前, 咱們先簡回顧一下 JVM 的類加載機制中的加載階段要作的三件事情
( 附 Java 中的類加載器 )maven

  1. 經過一個類的全名或其它途徑來獲取這個類的二進制字節流分佈式

  2. 將這個字節流所表明的靜態存儲結構轉化爲方法區的運行時數據結構ide

  3. 在內存中生成一個表明這個類的 Class 對象, 做爲方法區中對這個類訪問的入口

 

而咱們要說的動態代理,主要就發生在第一個階段, 這個階段類的二進制字節流的來源能夠有不少, 好比 zip 包、網絡、運行時計算生成、其它文件生成 (JSP)、數據庫獲取。其中運行時計算生成就是咱們所說的動態代理技術,在 Proxy 類中, 就是運用了 ProxyGenerator.generateProxyClass 來爲特定接口生成形式爲 *$Proxy 的代理類的二進制字節流。所謂的動態代理就是想辦法根據接口或者目標對象計算出代理類的字節碼而後加載進 JVM 中。實際計算的狀況會很複雜,咱們藉助一些諸如 JDK 動態代理實現、CGLIB 第三方庫來完成的

另外一方面爲了讓生成的代理類與目標對象 (就是委託類) 保持一致, 咱們有 2 種作法:經過接口的 JDK 動態代理 和經過繼承類的 CGLIB 動態代理。(還有一個使用了 ASM 框架的 javassist 太複雜了,我還沒研究過, 這裏TODO下)

3. JDK 動態代理

在 Java 的動態代理中, 主要涉及 2 個類,java.lang.reflect.Proxyjava.lang.reflect.InvocationHandler
咱們須要一個實現 InvocationHandler 接口的中間類, 這個接口只有一個方法 invoke 方法, 方法的每一個參數的註釋以下代碼。

咱們對處理類中的全部方法的調用都會變成對 invoke 方法的調用,這樣咱們能夠在 invoke 方法中添加統一的處理邏輯(也能夠根據 method 參數判斷是哪一個方法)。中間類 (實現了 InvocationHandler 的類) 有一個委託類對象引用, 在 Invoke 方法中調用了委託類對象的相應方法,經過這種聚合的方式持有委託類對象引用,把外部對 invoke 的調用最終都轉爲對委託類對象的調用。

實際上,中間類與委託類構成了靜態代理關係,在這個關係中,中間類是代理類,委託類是委託類。而後代理類與中間類也構成一個靜態代理關係,在這個關係中,中間類是委託類,代理類是代理類。也就是說,動態代理關係由兩組靜態代理關係組成,這就是動態代理的原理。

public interface InvocationHandler {
    /**
     * 調用處理
     * @param proxy 代理類對象
     * @param methon 標識具體調用的是代理類的哪一個方法
     * @param args 代理類方法的參數
     */
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

Demo 以下:

// 委託類接口
public interface IHelloService {

    /**
     * 方法1
     * @param userName
     * @return
     */
    String sayHello(String userName);

    /**
     * 方法2
     * @param userName
     * @return
     */
    String sayByeBye(String userName);

}
// 委託類
public class HelloService implements IHelloService {

    @Override
    public String sayHello(String userName) {
        System.out.println(userName + " hello");
        return userName + " hello";
    }

    @Override
    public String sayByeBye(String userName) {
        System.out.println(userName + " ByeBye");
        return userName + " ByeBye";
    }
}
// 中間類
public class JavaProxyInvocationHandler implements InvocationHandler {

    /**
     * 中間類持有委託類對象的引用,這裏會構成一種靜態代理關係
     */
    private Object obj ;

    /**
     * 有參構造器,傳入委託類的對象
     * @param obj 委託類的對象
     */
    public JavaProxyInvocationHandler(Object obj){
        this.obj = obj;

    }

    /**
     * 動態生成代理類對象,Proxy.newProxyInstance
     * @return 返回代理類的實例
     */
    public Object newProxyInstance() {
        return Proxy.newProxyInstance(
                //指定代理對象的類加載器
                obj.getClass().getClassLoader(),
                //代理對象須要實現的接口,能夠同時指定多個接口
                obj.getClass().getInterfaces(),
                //方法調用的實際處理者,代理對象的方法調用都會轉發到這裏
                this);
    }


    /**
     *
     * @param proxy 代理對象
     * @param method 代理方法
     * @param args 方法的參數
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("invoke before");
        Object result = method.invoke(obj, args);
        System.out.println("invoke after");
        return result;
    }
}
// 測試動態代理類
public class MainJavaProxy {
    public static void main(String[] args) {
        JavaProxyInvocationHandler proxyInvocationHandler = new JavaProxyInvocationHandler(new HelloService());
        IHelloService helloService = (IHelloService) proxyInvocationHandler.newProxyInstance();
        helloService.sayByeBye("paopao");
        helloService.sayHello("yupao");
    }

}

在上面的測試動態代理類中, 咱們調用 Proxy 類的 newProxyInstance 方法來獲取一個代理類實例。這個代理類實現了咱們指定的接口而且會把方法調用分發到指定的調用處理器。

首先經過 newProxyInstance 方法獲取代理類的實例, 以後就能夠經過這個代理類的實例調用代理類的方法,對代理類的方法調用都會調用中間類 (實現了 invocationHandle 的類) 的 invoke 方法,在 invoke 方法中咱們調用委託類的對應方法,而後加上本身的處理邏輯。

java 動態代理最大的特色就是動態生成的代理類和委託類實現同一個接口。java 動態代理其實內部是經過反射機制實現的,也就是已知的一個對象,在運行的時候動態調用它的方法,而且調用的時候還能夠加一些本身的邏輯在裏面。(附: Java 反射)

3.2 Proxy.newProxyInstance 源碼閱讀

上面說過, Proxy.newProxyInstance 經過反射機制用來動態生成代理類對象, 爲接口建立一個代理類,這個代理類實現這個接口。具體源碼以下:

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        // 檢查空指針
        Objects.requireNonNull(h);
        // 用原型實例指定建立對象的種類,而且經過拷貝這些原型建立新的對象
        final Class<?>[] intfs = interfaces.clone();
        // 獲取系統的安全接口,不爲空的話須要驗證是否容許訪問這種關係的代理訪問
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }

        /*
         * 生成代理類 Class,經過類加載器和接口
         */
        Class<?> cl = getProxyClass0(loader, intfs);

        /*
         * 經過構造器來建立實例
         */
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }
            //獲取全部的構造器
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            // 構造器不是public的話須要設置能夠訪問
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            // 返回建立的代理類Class的實例對象
            return cons.newInstance(new Object[]{h});
        } catch (IllegalAccessException|InstantiationException e) {
            throw new InternalError(e.toString(), e);
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InternalError(t.toString(), t);
            }
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString(), e);
        }
    }

4. CGLIB 動態代理

JDK 動態代理依賴接口實現,而當咱們只有類沒有接口的時候就須要使用另外一種動態代理技術 CGLIB 動態代理。首先 CGLIB 動態代理是第三方框架實現的,在 maven 工程中咱們須要引入 cglib 的包, 以下:

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>2.2</version>
</dependency>

CGLIB 代理是針對類來實現代理的,原理是對指定的委託類生成一個子類並重寫其中業務方法來實現代理。代理類對象是由 Enhancer 類建立的。CGLIB 建立動態代理類的模式是:

  1. 查找目標類上的全部非 final 的 public 類型的方法 (final 的不能被重寫)

  2. 將這些方法的定義轉成字節碼

  3. 將組成的字節碼轉換成相應的代理的 Class 對象而後經過反射得到代理類的實例對象

  4. 實現 MethodInterceptor 接口, 用來處理對代理類上全部方法的請求

 

// 委託類,是一個簡單類
public class CglibHelloClass {
    /**
     * 方法1
     * @param userName
     * @return
     */
    public String sayHello(String userName){
        System.out.println("目標對象的方法執行了");
        return userName + " sayHello";
    }

    public String sayByeBye(String userName){
        System.out.println("目標對象的方法執行了");
        return userName + " sayByeBye";
    }

}
/**
 * CglibInterceptor 用於對方法調用攔截以及回調
 *
 */
public class CglibInterceptor implements MethodInterceptor {
    /**
     * CGLIB 加強類對象,代理類對象是由 Enhancer 類建立的,
     * Enhancer 是 CGLIB 的字節碼加強器,能夠很方便的對類進行拓展
     */
    private Enhancer enhancer = new Enhancer();

    /**
     *
     * @param obj  被代理的對象
     * @param method 代理的方法
     * @param args 方法的參數
     * @param proxy CGLIB方法代理對象
     * @return  cglib生成用來代替Method對象的一個對象,使用MethodProxy比調用JDK自身的Method直接執行方法效率會有提高
     * @throws Throwable
     */
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("方法調用以前");
        Object o = proxy.invokeSuper(obj, args);
        System.out.println("方法調用以後");
        return o;
    }


    /**
     * 使用動態代理建立一個代理對象
     * @param c
     * @return
     */
    public  Object newProxyInstance(Class<?> c) {
        /**
         * 設置產生的代理對象的父類,加強類型
         */
        enhancer.setSuperclass(c);
        /**
         * 定義代理邏輯對象爲當前對象,要求當前對象實現 MethodInterceptor 接口
         */
        enhancer.setCallback(this);
        /**
         * 使用默認無參數的構造函數建立目標對象,這是一個前提,被代理的類要提供無參構造方法
         */
        return enhancer.create();
    }
}

//測試類
public class MainCglibProxy {
    public static void main(String[] args) {
        CglibProxy cglibProxy = new CglibProxy();
        CglibHelloClass cglibHelloClass = (CglibHelloClass) cglibProxy.newProxyInstance(CglibHelloClass.class);
        cglibHelloClass.sayHello("isole");
        cglibHelloClass.sayByeBye("sss");
    }
}

對於須要被代理的類,它只是動態生成一個子類以覆蓋非 final 的方法,同時綁定鉤子回調自定義的攔截器。值得說的是,它比 JDK 動態代理還要快。值得注意的是,咱們傳入目標類做爲代理的父類。不一樣於 JDK 動態代理,咱們不能使用目標對象來建立代理。目標對象只能被 CGLIB 建立。在例子中,默認的無參構造方法被使用來建立目標對象。

總結

 

靜態代理比較容易理解, 須要被代理的類和代理類實現自同一個接口, 而後在代理類中調用真正實現類, 而且靜態代理的關係在編譯期間就已經肯定了。而動態代理的關係是在運行期間肯定的。靜態代理實現簡單,適合於代理類較少且肯定的狀況,而動態代理則給咱們提供了更大的靈活性。

 

JDK 動態代理所用到的代理類在程序調用到代理類對象時才由 JVM 真正建立,JVM 根據傳進來的 業務實現類對象 以及 方法名 ,動態地建立了一個代理類的 class 文件並被字節碼引擎執行,而後經過該代理類對象進行方法調用。咱們須要作的,只需指定代理類的預處理、調用後操做便可。

 

靜態代理和動態代理都是基於接口實現的, 而對於那些沒有提供接口只是提供了實現類的而言, 就只能選擇 CGLIB 動態代理了

 

JDK 動態代理和 CGLIB 動態代理的區別
 

  • JDK 動態代理基於 Java 反射機制實現, 必需要實現了接口的業務類才能用這種方法生成代理對象。

  • CGLIB 動態代理基於 ASM 框架經過生成業務類的子類來實現。

  • JDK 動態代理的優點是最小化依賴關係,減小依賴意味着簡化開發和維護而且有 JDK 自身支持。還能夠平滑進行 JDK 版本升級,代碼實現簡單。基於 CGLIB 框架的優點是無須實現接口,達到代理類無侵入,咱們只需操做咱們關係的類,沒必要爲其它相關類增長工做量,性能比較高。

 

描述代理的幾種實現方式? 分別說出優缺點?
 

代理能夠分爲 "靜態代理" 和 "動態代理",動態代理又分爲 "JDK 動態代理" 和 "CGLIB 動態代理" 實現。

 

靜態代理:代理對象和實際對象都繼承了同一個接口,在代理對象中指向的是實際對象的實例,這樣對外暴露的是代理對象而真正調用的是 Real Object.

 

  • 優勢:能夠很好的保護實際對象的業務邏輯對外暴露,從而提升安全性。

  •  

    缺點:不一樣的接口要有不一樣的代理類實現,會很冗餘

     

 

 

 

JDK 動態代理
爲了解決靜態代理中,生成大量的代理類形成的冗餘;
JDK 動態代理只須要實現 InvocationHandler 接口,重寫 invoke 方法即可以完成代理的實現,

 

 

jdk 的代理是利用反射生成代理類 Proxyxx.class 代理類字節碼,並生成對象
jdk 動態代理之因此只能代理接口是由於代理類自己已經 extends 了 Proxy,而 java 是不容許多重繼承的,可是容許實現多個接口

  • 優勢:解決了靜態代理中冗餘的代理實現類問題。

  • 缺點:JDK 動態代理是基於接口設計實現的,若是沒有接口,會拋異常。

 

 

CGLIB 代理:
因爲 JDK 動態代理限制了只能基於接口設計,而對於沒有接口的狀況,JDK 方式解決不了;
CGLib 採用了很是底層的字節碼技術,其原理是經過字節碼技術爲一個類建立子類,並在子類中採用方法攔截的技術攔截全部父類方法的調用,順勢織入橫切邏輯,來完成動態代理的實現。
實現方式實現 MethodInterceptor 接口,重寫 intercept 方法,經過 Enhancer 類的回調方法來實現。

 

可是 CGLib 在建立代理對象時所花費的時間卻比 JDK 多得多,因此對於單例的對象,由於無需頻繁建立對象,用 CGLib 合適,反之,使用 JDK 方式要更爲合適一些。
同時,因爲 CGLib 因爲是採用動態建立子類的方法,對於 final 方法,沒法進行代理。

 

優勢:沒有接口也能實現動態代理,並且採用字節碼加強技術,性能也不錯。
缺點:技術實現相對難理解些。

 

免費Java高級資料,須要本身關注公衆號領取,涵蓋了Java、Redis、MongoDB、MySQL、Zookeeper、Spring Cloud、Dubbo高併發分佈式等教程。

相關文章
相關標籤/搜索