溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Java 的代理機(jī)制是什么

發(fā)布時(shí)間:2021-06-21 15:56:03 來(lái)源:億速云 閱讀:161 作者:chen 欄目:開(kāi)發(fā)技術(shù)

這篇文章主要介紹“Java 的代理機(jī)制是什么”,在日常操作中,相信很多人在Java 的代理機(jī)制是什么問(wèn)題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”Java 的代理機(jī)制是什么”的疑惑有所幫助!接下來(lái),請(qǐng)跟著小編一起來(lái)學(xué)習(xí)吧!

目錄
  • 一、常規(guī)編碼方式

  • 二、代理模式概述

  • 三、靜態(tài)代理

    • 3.1、什么是靜態(tài)代理

    • 3.2、代碼示例

  • 四、Java 字節(jié)碼生成框架

    • 五、什么是動(dòng)態(tài)代理

      • 六、JDK 動(dòng)態(tài)代理機(jī)制

        • 6.1、使用步驟

        • 6.2、代碼示例

      • 七、CGLIB 動(dòng)態(tài)代理機(jī)制

        • 7.1、使用步驟

        • 7.2、代碼示例

      • 八、什么情況下使用動(dòng)態(tài)代理

        • 九、靜態(tài)代理和動(dòng)態(tài)代理對(duì)比

          • 十、總結(jié)

            一、常規(guī)編碼方式

            在學(xué)習(xí)代理之前,先回顧以下我們的常規(guī)編碼方式:所有 interface 類型的變量總是通過(guò)向上轉(zhuǎn)型并指向某個(gè)實(shí)例的。

            1)首先,定義一個(gè)接口:

            public interface SmsService {
                String send(String message);
            }

            2)然后編寫其實(shí)現(xiàn)類:

            public class SmsServicseImpl implements SmsService {
                public String send(String message) {
                    System.out.println("send message:" + message);
                    return message;
                }
            }

            3)最后創(chuàng)建該實(shí)現(xiàn)類的實(shí)例,轉(zhuǎn)型為接口并調(diào)用:

            SmsService s = new SmsServicseImpl();
            s.send("Java");

            上述這種方式就是我們通常編寫代碼的方式。而代理模式和這種方式有很大的區(qū)別,且看下文。

            二、代理模式概述

            簡(jiǎn)單來(lái)說(shuō),代理模式就是 使用代理對(duì)象來(lái)代替對(duì)真實(shí)對(duì)象的訪問(wèn),這樣就可以在不修改原目標(biāo)對(duì)象的前提下,提供額外的功能操作,擴(kuò)展目標(biāo)對(duì)象的功能。

            代理模式大致有三種角色:

            • Real Subject:真實(shí)類,也就是被代理類、委托類。用來(lái)真正完成業(yè)務(wù)服務(wù)功能;

            • Proxy:代理類。將自身的請(qǐng)求用 Real Subject 對(duì)應(yīng)的功能來(lái)實(shí)現(xiàn),代理類對(duì)象并不真正的去實(shí)現(xiàn)其業(yè)務(wù)功能;

            • Subject:定義 RealSubject 和 Proxy 角色都應(yīng)該實(shí)現(xiàn)的接口。

            Java 的代理機(jī)制是什么

            通俗來(lái)說(shuō),代理模式的主要作用是擴(kuò)展目標(biāo)對(duì)象的功能,比如說(shuō)在目標(biāo)對(duì)象的某個(gè)方法執(zhí)行前后你可以增加一些額外的操作,并且不用修改這個(gè)方法的原有代碼。如果大家學(xué)過(guò) Spring 的 AOP,一定能夠很好的理解這句話。

            舉個(gè)例子:你找了小紅來(lái)幫你向小綠問(wèn)話,小紅就看作是代理我的代理類 Proxy,而你是 Real Subject,因?yàn)樾〖t要傳達(dá)的話其實(shí)是你說(shuō)的。那么你和小紅都需要實(shí)現(xiàn)的接口(Subject)就是說(shuō)話,由于你倆都能說(shuō)話,在外界看來(lái)你倆就是一樣的(滑稽,大家理解就好,不用較真)

            Java 的代理機(jī)制是什么

            看到這里,不知道大家能不能理解了為什么委托類和代理類都需要實(shí)現(xiàn)相同的接口?

            那是為了保持行為的一致性,在訪問(wèn)者看來(lái)兩者之間就沒(méi)有區(qū)別。這樣,通過(guò)代理類這個(gè)中間層,很好地隱藏和保護(hù)了委托類對(duì)象,能有效屏蔽外界對(duì)委托類對(duì)象的直接訪問(wèn)。同時(shí),也可以在代理類上加上額外的操作,比如小紅在說(shuō)話之前會(huì)跳一段舞,外界就會(huì)覺(jué)得你在說(shuō)話前會(huì)跳一段舞,所以,這就實(shí)現(xiàn)了委托類的功能增強(qiáng)。

            代理模式有靜態(tài)代理和動(dòng)態(tài)代理兩種實(shí)現(xiàn)方式。

            三、靜態(tài)代理

            3.1、什么是靜態(tài)代理

            先來(lái)看靜態(tài)代理的實(shí)現(xiàn)步驟:

            1)定義一個(gè)接口(Subject)

            2)創(chuàng)建一個(gè)委托類(Real Subject)實(shí)現(xiàn)這個(gè)接口

            3)創(chuàng)建一個(gè)代理類(Proxy)同樣實(shí)現(xiàn)這個(gè)接口

            4)將委托類 Real Subject 注入進(jìn)代理類 Proxy,在代理類的方法中調(diào)用 Real Subject 中的對(duì)應(yīng)方法。這樣的話,我們就可以通過(guò)代理類屏蔽對(duì)目標(biāo)對(duì)象的訪問(wèn),并且可以在目標(biāo)方法執(zhí)行前后做一些自己想做的事情。

            從實(shí)現(xiàn)和應(yīng)用角度來(lái)說(shuō),靜態(tài)代理中,我們對(duì)目標(biāo)對(duì)象的每個(gè)方法的增強(qiáng)都是手動(dòng)完成的,非常不靈活(比如接口一旦新增加方法,目標(biāo)對(duì)象和代理對(duì)象都要進(jìn)行修改)且麻煩(需要對(duì)每個(gè)目標(biāo)類都單獨(dú)寫一個(gè)代理類)。 實(shí)際應(yīng)用場(chǎng)景非常非常少,日常開(kāi)發(fā)幾乎看不到使用靜態(tài)代理的場(chǎng)景。

            從 JVM 層面來(lái)說(shuō), 靜態(tài)代理在編譯時(shí)就將接口、委托類、代理類這些都變成了一個(gè)個(gè)實(shí)際的 .class 文件。

            3.2、代碼示例

            1)定義發(fā)送短信的接口

            public interface SmsService {
                String send(String message);
            }

            2)創(chuàng)建一個(gè)委托類(Real Subject)實(shí)現(xiàn)這個(gè)接口

            public class SmsServiceImpl implements SmsService {
                public String send(String message) {
                    System.out.println("send message:" + message);
                    return message;
                }
            }

            3)創(chuàng)建一個(gè)代理類(Proxy)同樣實(shí)現(xiàn)這個(gè)接口

            4)將委托類 Real Subject 注入進(jìn)代理類 Proxy,在代理類的方法中調(diào)用 Real Subject 中的對(duì)應(yīng)方法。這樣的話,我們就可以通過(guò)代理類屏蔽對(duì)目標(biāo)對(duì)象的訪問(wèn),并且可以在目標(biāo)方法執(zhí)行前后做一些自己想做的事情。

            public class SmsProxy implements SmsService {
                
                // 將委托類注入進(jìn)代理類
                private final SmsService smsService;
            
                public SmsProxy(SmsService smsService) {
                    this.smsService = smsService;
                }
            
                @Override
                public String send(String message) {
                    // 調(diào)用委托類方法之前,我們可以添加自己的操作
                    System.out.println("before method send()");
                    // 調(diào)用委托類方法
                    smsService.send(message); 
                    // 調(diào)用委托類方法之后,我們同樣可以添加自己的操作
                    System.out.println("after method send()");
                    return null;
                }
            }

            那么,如何使用這個(gè)被增強(qiáng)的 send 方法呢?

            public class Main {
                public static void main(String[] args) {
                    SmsService smsService = new SmsServiceImpl();
                    SmsProxy smsProxy = new SmsProxy(smsService);
                    smsProxy.send("Java");
                }
            }

            運(yùn)行上述代碼之后,控制臺(tái)打印出:

            before method send()

            send message:java

            after method send()

            從輸出結(jié)果可以看出,我們已經(jīng)增強(qiáng)了委托類 SmsServiceImplsend() 方法。

            當(dāng)然,從上述代碼我們也能看出來(lái),靜態(tài)代理存在一定的弊端。假如說(shuō)我們現(xiàn)在新增了一個(gè)委托類實(shí)現(xiàn)了 SmsService 接口,如果我們想要對(duì)這個(gè)委托類進(jìn)行增強(qiáng),就需要重新寫一個(gè)代理類,然后注入這個(gè)新的委托類,非常不靈活。也就是說(shuō)靜態(tài)代理是一個(gè)委托了對(duì)應(yīng)一個(gè)代理類,能不能將代理類做成一個(gè)通用的呢?為此,動(dòng)態(tài)代理應(yīng)用而生。

            四、Java 字節(jié)碼生成框架

            在講解動(dòng)態(tài)之前,我們有必要詳細(xì)說(shuō)一下 .class 字節(jié)碼文件這個(gè)東西。動(dòng)態(tài)代理機(jī)制和 Java 字節(jié)碼生成框架息息相關(guān)。

            在上文反射中我們提到,一個(gè) Class 類對(duì)應(yīng)一個(gè) .class 字節(jié)碼文件,也就說(shuō)字節(jié)碼文件中存儲(chǔ)了一個(gè)類的全部信息。字節(jié)碼其實(shí)是二進(jìn)制文件,內(nèi)容是只有 JVM 能夠識(shí)別的機(jī)器碼。

            解析過(guò)程這樣的:JVM 讀取 .class 字節(jié)碼文件,取出二進(jìn)制數(shù)據(jù),加載到內(nèi)存中,解析字節(jié)碼文件內(nèi)的信息,生成對(duì)應(yīng)的 Class 類對(duì)象:

            Java 的代理機(jī)制是什么

            顯然,上述這個(gè)過(guò)程是在編譯期就發(fā)生的。

            那么,由于JVM 是通過(guò) .class 字節(jié)碼文件(也就是二進(jìn)制信息)加載類的,如果我們?cè)谶\(yùn)行期遵循 Java 編譯系統(tǒng)組織 .class 字節(jié)碼文件的格式和結(jié)構(gòu),生成相應(yīng)的二進(jìn)制數(shù)據(jù),然后再把這個(gè)二進(jìn)制數(shù)據(jù)加載轉(zhuǎn)換成對(duì)應(yīng)的類。這樣,我們不就完成了在運(yùn)行時(shí)動(dòng)態(tài)的創(chuàng)建一個(gè)類。這個(gè)思想其實(shí)也就是動(dòng)態(tài)代理的思想。

            Java 的代理機(jī)制是什么

            在運(yùn)行時(shí)期按照 JVM 規(guī)范對(duì) .class 字節(jié)碼文件的組織規(guī)則,生成對(duì)應(yīng)的二進(jìn)制數(shù)據(jù)。當(dāng)前有很多開(kāi)源框架可以完成這個(gè)功能,如

            • ASM

            • CGLIB

            • Javassist

            • ......

            需要注意的是,CGLIB 是基于 ASM 的。 這里簡(jiǎn)單對(duì)比一下 ASM 和 Javassist:

            • Javassist 源代碼級(jí) API 比 ASM 中實(shí)際的字節(jié)碼操作更容易使用

            • Javassist 在復(fù)雜的字節(jié)碼級(jí)操作上提供了更高級(jí)別的抽象層。Javassist 源代碼級(jí) API 只需要很少的字節(jié)碼知識(shí),甚至不需要任何實(shí)際字節(jié)碼知識(shí),因此實(shí)現(xiàn)起來(lái)更容易、更快。

            • Javassist 使用反射機(jī)制,這使得它比 ASM 慢。

            總的來(lái)說(shuō) ASM 比 Javassist 快得多,并且提供了更好的性能,但是 Javassist 相對(duì)來(lái)說(shuō)更容易使用,兩者各有千秋。

            以 Javassist 為例,我們來(lái)看看這些框架在運(yùn)行時(shí)生成 .class 字節(jié)碼文件的強(qiáng)大能力。

            正常來(lái)說(shuō),我們創(chuàng)建一個(gè)類的代碼是這樣的:

            package com.samples;
            
            public class Programmer {
                public void code(){
                    System.out.println("I'm a Programmer,Just Coding.....");
                }
            }

            下面通過(guò) Javassist 創(chuàng)建和上面一模一樣的 Programmer 類的字節(jié)碼:

            import javassist.ClassPool;
            import javassist.CtClass;
            import javassist.CtMethod;
            import javassist.CtNewMethod;
            
            public class MyGenerator {
                public static void main(String[] args) throws Exception {
                    ClassPool pool = ClassPool.getDefault();
                    // 創(chuàng)建 Programmer 類      
                    CtClass cc= pool.makeClass("com.samples.Programmer");
                    // 定義方法
                    CtMethod method = CtNewMethod.make("public void code(){}", cc);
                    // 插入方法代碼
                    method.insertBefore("System.out.println(\"I'm a Programmer,Just Coding.....\");");
                    cc.addMethod(method);
                    // 保存生成的字節(jié)碼
                    cc.writeFile("d://temp");
                }
            }

            通過(guò)反編譯工具打開(kāi) Programmer.class 可以看到以下代碼:

            Java 的代理機(jī)制是什么

            五、什么是動(dòng)態(tài)代理

            OK,了解了 Java 字節(jié)碼生成框架,可以開(kāi)始學(xué)習(xí)動(dòng)態(tài)代理(Dynamic Proxy)了。

            回顧一下靜態(tài)代理,我們把靜態(tài)代理的執(zhí)行過(guò)程抽象為下圖:

            Java 的代理機(jī)制是什么

            可以看見(jiàn),代理類無(wú)非是在調(diào)用委托類方法的前后增加了一些操作。委托類的不同,也就導(dǎo)致代理類的不同。

            那么為了做一個(gè)通用性的代理類出來(lái),我們把調(diào)用委托類方法的這個(gè)動(dòng)作抽取出來(lái),把它封裝成一個(gè)通用性的處理類,于是就有了動(dòng)態(tài)代理中的 InvocationHandler 角色(處理類)。

            于是,在代理類和委托類之間就多了一個(gè)處理類的角色,這個(gè)角色主要是對(duì)代理類調(diào)用委托類方法的這個(gè)動(dòng)作進(jìn)行統(tǒng)一的調(diào)用,也就是由 InvocationHandler 來(lái)統(tǒng)一處理代理類調(diào)用委托類方法這個(gè)操作。看下圖:

            Java 的代理機(jī)制是什么

            從 JVM 角度來(lái)說(shuō),動(dòng)態(tài)代理是在運(yùn)行時(shí)動(dòng)態(tài)生成 .class 字節(jié)碼文件 ,并加載到 JVM 中的。這個(gè)我們?cè)?Java 字節(jié)碼生成框架中已經(jīng)提到過(guò)。

            雖然動(dòng)態(tài)代理在我們?nèi)粘i_(kāi)發(fā)中使用的相對(duì)較少,但是在框架中的幾乎是必用的一門技術(shù)。學(xué)會(huì)了動(dòng)態(tài)代理之后,對(duì)于我們理解和學(xué)習(xí)各種框架的原理也非常有幫助,Spring AOP、RPC 等框架的實(shí)現(xiàn)都依賴了動(dòng)態(tài)代理。

            就 Java 來(lái)說(shuō),動(dòng)態(tài)代理的實(shí)現(xiàn)方式有很多種,比如:

            • JDK 動(dòng)態(tài)代理

            • CGLIB 動(dòng)態(tài)代理

            • Javassit 動(dòng)態(tài)代理

            • ......

            下面詳細(xì)講解這三種動(dòng)態(tài)代理機(jī)制。

            六、JDK 動(dòng)態(tài)代理機(jī)制

            6.1、使用步驟

            先來(lái)看下 JDK 動(dòng)態(tài)代理機(jī)制的使用步驟:

            1)定義一個(gè)接口(Subject)

            2)創(chuàng)建一個(gè)委托類(Real Subject)實(shí)現(xiàn)這個(gè)接口

            3)創(chuàng)建一個(gè)處理類并實(shí)現(xiàn) InvocationHandler 接口,重寫其 invoke 方法(在 invoke 方法中利用反射機(jī)制調(diào)用委托類的方法,并自定義一些處理邏輯),并將委托類注入處理類

            Java 的代理機(jī)制是什么

            該方法有下面三個(gè)參數(shù):

            proxy:代理類對(duì)象(見(jiàn)下一步)

            method:還記得我們?cè)谏掀恼路瓷渲兄v到的 Method.invoke 嗎?就是這個(gè),我們可以通過(guò)它來(lái)調(diào)用委托類的方法(反射)

            Java 的代理機(jī)制是什么

            args:傳給委托類方法的參數(shù)列表

            4)創(chuàng)建代理對(duì)象(Proxy):通過(guò) Proxy.newProxyInstance() 創(chuàng)建委托類對(duì)象的代理對(duì)象

            Java 的代理機(jī)制是什么

            這個(gè)方法需要 3 個(gè)參數(shù):

            • 類加載器 ClassLoader

            • 委托類實(shí)現(xiàn)的接口數(shù)組,至少需要傳入一個(gè)接口進(jìn)去

            • 調(diào)用的 InvocationHandler 實(shí)例處理接口方法(也就是第 3 步我們創(chuàng)建的類的實(shí)例)

            也就是說(shuō):我們?cè)谕ㄟ^(guò) Proxy 類的 newProxyInstance() 創(chuàng)建的代理對(duì)象在調(diào)用方法的時(shí)候,實(shí)際會(huì)調(diào)用到實(shí)現(xiàn)了 InvocationHandler 接口的處理類的 invoke()方法,可以在 invoke() 方法中自定義處理邏輯,比如在方法執(zhí)行前后做什么事情。

            6.2、代碼示例

            1)定義一個(gè)接口(Subject)

            public interface SmsService {
                String send(String message);
            }

            2)創(chuàng)建一個(gè)委托類(Real Subject)實(shí)現(xiàn)這個(gè)接口

            public class SmsServiceImpl implements SmsService {
                public String send(String message) {
                    System.out.println("send message:" + message);
                    return message;
                }
            }

            3)創(chuàng)建一個(gè)處理類并實(shí)現(xiàn) InvocationHandler 接口,重寫其 invoke 方法(在 invoke 方法中利用反射機(jī)制調(diào)用委托類的方法,并自定義一些處理邏輯),并將委托類注入處理類

            import java.lang.reflect.InvocationHandler;
            import java.lang.reflect.InvocationTargetException;
            import java.lang.reflect.Method;
            
            public class DebugInvocationHandler implements InvocationHandler {
                
                // 將委托類注入處理類(這里我們用 Object 代替,方便擴(kuò)展)
                private final Object target;
            
                public DebugInvocationHandler(Object target) {
                    this.target = target;
                }
                
                // 重寫 invoke 方法
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
                    //調(diào)用方法之前,我們可以添加自己的操作
                    System.out.println("before method " + method.getName());
                    Object result = method.invoke(target, args);
                    //調(diào)用方法之后,我們同樣可以添加自己的操作
                    System.out.println("after method " + method.getName());
                    return result;
                }
            }

            4)定義一個(gè)創(chuàng)建代理對(duì)象(Proxy)的工廠類:通過(guò) Proxy.newProxyInstance() 創(chuàng)建委托類對(duì)象的代理對(duì)象

            public class JdkProxyFactory {
                public static Object getProxy(Object target) {
                    return Proxy.newProxyInstance(
                            target.getClass().getClassLoader(),
                            target.getClass().getInterfaces(),
                            new DebugInvocationHandler(target)
                    );
                }
            }

            5)實(shí)際使用

            SmsService smsService = (SmsService) JdkProxyFactory.getProxy(new SmsServiceImpl());
            smsService.send("Java");

            運(yùn)行上述代碼之后,控制臺(tái)打印出:

            before method send

            send message:Java

            after method send

            七、CGLIB 動(dòng)態(tài)代理機(jī)制

            7.1、使用步驟

            JDK 動(dòng)態(tài)代理有一個(gè)最致命的問(wèn)題是它只能代理實(shí)現(xiàn)了某個(gè)接口的實(shí)現(xiàn)類,并且代理類也只能代理接口中實(shí)現(xiàn)的方法,要是實(shí)現(xiàn)類中有自己私有的方法,而接口中沒(méi)有的話,該方法不能進(jìn)行代理調(diào)用。

            為了解決這個(gè)問(wèn)題,我們可以用 CGLIB 動(dòng)態(tài)代理機(jī)制。

            上文也提到過(guò),CGLIB(Code Generation Library)是一個(gè)基于 ASM 的 Java 字節(jié)碼生成框架,它允許我們?cè)谶\(yùn)行時(shí)對(duì)字節(jié)碼進(jìn)行修改和動(dòng)態(tài)生成。原理就是通過(guò)字節(jié)碼技術(shù)生成一個(gè)子類,并在子類中攔截父類方法的調(diào)用,織入額外的業(yè)務(wù)邏輯。關(guān)鍵詞大家注意到?jīng)]有,攔截!CGLIB 引入一個(gè)新的角色就是方法攔截器 MethodInterceptor。和 JDK 中的處理類 InvocationHandler 差不多,也是用來(lái)實(shí)現(xiàn)方法的統(tǒng)一調(diào)用的??聪聢D:

            Java 的代理機(jī)制是什么

            另外由于 CGLIB 采用繼承的方式,所以被代理的類不能被 final 修飾。

            很多知名的開(kāi)源框架都使用到了 CGLIB, 例如 Spring 中的 AOP 模塊中:如果目標(biāo)對(duì)象實(shí)現(xiàn)了接口,則默認(rèn)采用 JDK 動(dòng)態(tài)代理,否則采用 CGLIB 動(dòng)態(tài)代理。

            來(lái)看 CGLIB 動(dòng)態(tài)代理的使用步驟:

            1)首先創(chuàng)建一個(gè)委托類(Real Subject)

            2)創(chuàng)建一個(gè)方法攔截器實(shí)現(xiàn)接口 MethodInterceptor,并重寫 intercept 方法。intercept 用于攔截并增強(qiáng)委托類的方法(和 JDK 動(dòng)態(tài)代理 InvocationHandler 中的 invoke 方法類似)

            Java 的代理機(jī)制是什么

            該方法擁有四個(gè)參數(shù):

            • Object var1:委托類對(duì)象

            • Method var2:被攔截的方法(委托類中需要增強(qiáng)的方法)

            • Object[] var3:方法入?yún)?/p>

            • MethodProxy var4:用于調(diào)用委托類的原始方法(底層也是通過(guò)反射機(jī)制,不過(guò)不是 Method.invoke 了,而是使用 MethodProxy.invokeSuper 方法)

            Java 的代理機(jī)制是什么

            3)創(chuàng)建代理對(duì)象(Proxy):通過(guò) Enhancer.create() 創(chuàng)建委托類對(duì)象的代理對(duì)象

            Java 的代理機(jī)制是什么

            也就是說(shuō):我們?cè)谕ㄟ^(guò) Enhancer 類的 create() 創(chuàng)建的代理對(duì)象在調(diào)用方法的時(shí)候,實(shí)際會(huì)調(diào)用到實(shí)現(xiàn)了 MethodInterceptor 接口的處理類的 intercept()方法,可以在 intercept() 方法中自定義處理邏輯,比如在方法執(zhí)行前后做什么事情。

            可以發(fā)現(xiàn),CGLIB 動(dòng)態(tài)代理機(jī)制和 JDK 動(dòng)態(tài)代理機(jī)制的步驟差不多,CGLIB 動(dòng)態(tài)代理的核心是方法攔截器 MethodInterceptorEnhancer,而 JDK 動(dòng)態(tài)代理的核心是處理類 InvocationHandlerProxy。

            7.2、代碼示例

            不同于 JDK 動(dòng)態(tài)代理不需要額外的依賴。CGLIB 是一個(gè)開(kāi)源項(xiàng)目,如果你要使用它的話,需要手動(dòng)添加相關(guān)依賴。

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

            1)首先創(chuàng)建一個(gè)委托類(Real Subject)

            public class AliSmsService {
                public String send(String message) {
                    System.out.println("send message:" + message);
                    return message;
                }
            }

            2)創(chuàng)建一個(gè)方法攔截器實(shí)現(xiàn)接口 MethodInterceptor,并重寫 intercept 方法

            import net.sf.cglib.proxy.MethodInterceptor;
            import net.sf.cglib.proxy.MethodProxy;
            import java.lang.reflect.Method;
            
            public class DebugMethodInterceptor implements MethodInterceptor {
            
                @Override
                public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                    // 調(diào)用方法之前,我們可以添加自己的操作
                    System.out.println("before method " + method.getName());
                    // 通過(guò)反射調(diào)用委托類的方法
                    Object object = methodProxy.invokeSuper(o, args);
                    // 調(diào)用方法之后,我們同樣可以添加自己的操作
                    System.out.println("after method " + method.getName());
                    return object;
                }
            
            }

            3)創(chuàng)建代理對(duì)象(Proxy):通過(guò) Enhancer.create() 創(chuàng)建委托類對(duì)象的代理對(duì)象

            import net.sf.cglib.proxy.Enhancer;
            
            public class CglibProxyFactory {
                public static Object getProxy(Class<?> clazz) {
                    // 創(chuàng)建動(dòng)態(tài)代理增強(qiáng)類
                    Enhancer enhancer = new Enhancer();
                    // 設(shè)置類加載器
                    enhancer.setClassLoader(clazz.getClassLoader());
                    // 設(shè)置委托類(設(shè)置父類)
                    enhancer.setSuperclass(clazz);
                    // 設(shè)置方法攔截器
                    enhancer.setCallback(new DebugMethodInterceptor());
                    // 創(chuàng)建代理類
                    return enhancer.create();
                }
            }

            setSuperclass 我們就能看出,為什么說(shuō) CGLIB 是基于繼承的。

            4)實(shí)際使用

            AliSmsService aliSmsService = 
                (AliSmsService) CglibProxyFactory.getProxy(AliSmsService.class);
            aliSmsService.send("Java");

            運(yùn)行上述代碼之后,控制臺(tái)打印出:

            before method send

            send message:Java

            after method send

            JDK 動(dòng)態(tài)代理和 CGLIB 動(dòng)態(tài)代理對(duì)比

            1)JDK 動(dòng)態(tài)代理是基于實(shí)現(xiàn)了接口的委托類,通過(guò)接口實(shí)現(xiàn)代理;而 CGLIB 動(dòng)態(tài)代理是基于繼承了委托類的子類,通過(guò)子類實(shí)現(xiàn)代理。

            2)JDK 動(dòng)態(tài)代理只能代理實(shí)現(xiàn)了接口的類,且只能增強(qiáng)接口中現(xiàn)有的方法;而 CGLIB 可以代理未實(shí)現(xiàn)任何接口的類。

            3)就二者的效率來(lái)說(shuō),大部分情況都是 JDK 動(dòng)態(tài)代理的效率更高,隨著 JDK 版本的升級(jí),這個(gè)優(yōu)勢(shì)更加明顯。

            常見(jiàn)的還有 Javassist 動(dòng)態(tài)代理機(jī)制。和 CGLIB 一樣,作為一個(gè) Java 字節(jié)碼生成框架,Javassist 天生就擁有在運(yùn)行時(shí)動(dòng)態(tài)創(chuàng)建一個(gè)類的能力,實(shí)現(xiàn)動(dòng)態(tài)代理自然不在話下。 Dubbo 就是默認(rèn)使用 Javassit 來(lái)進(jìn)行動(dòng)態(tài)代理的。

            八、什么情況下使用動(dòng)態(tài)代理

            1)設(shè)計(jì)模式中有一個(gè)設(shè)計(jì)原則是開(kāi)閉原則,即對(duì)修改關(guān)閉,對(duì)擴(kuò)展開(kāi)放,我們?cè)诠ぷ髦杏袝r(shí)會(huì)接手很多前人的代碼,里面代碼邏輯讓人摸不著頭腦,就很難去下手修改代碼,那么這時(shí)我們就可以通過(guò)代理對(duì)類進(jìn)行增強(qiáng)。

            2)我們?cè)谑褂?RPC 框架的時(shí)候,框架本身并不能提前知道各個(gè)業(yè)務(wù)方要調(diào)用哪些接口的哪些方法 。那么這個(gè)時(shí)候,就可用通過(guò)動(dòng)態(tài)代理的方式來(lái)建立一個(gè)中間人給客戶端使用,也方便框架進(jìn)行搭建邏輯,某種程度上也是客戶端代碼和框架松耦合的一種表現(xiàn)。

            3)Spring 的 AOP 機(jī)制同樣也是采用了動(dòng)態(tài)代理,此處不做詳細(xì)討論。

            九、靜態(tài)代理和動(dòng)態(tài)代理對(duì)比

            1)靈活性 :動(dòng)態(tài)代理更加靈活,不需要必須實(shí)現(xiàn)接口,可以直接代理實(shí)現(xiàn)類,并且可以不需要針對(duì)每個(gè)目標(biāo)類都創(chuàng)建一個(gè)代理類。另外,靜態(tài)代理中,接口一旦新增加方法,目標(biāo)對(duì)象和代理對(duì)象都要進(jìn)行修改,這是非常麻煩的

            2)JVM 層面 :靜態(tài)代理在編譯時(shí)就將接口、實(shí)現(xiàn)類、代理類這些都變成了一個(gè)個(gè)實(shí)際的 .class 字節(jié)碼文件。而動(dòng)態(tài)代理是在運(yùn)行時(shí)動(dòng)態(tài)生成類字節(jié)碼,并加載到 JVM 中的。

            十、總結(jié)

            全部捋一遍下來(lái)還是收獲蠻多的,我感覺(jué)只要理解了字節(jié)碼在編譯期生成還是在運(yùn)行期生成,就差不多能夠把握住靜態(tài)代理和動(dòng)態(tài)代理了??偨Y(jié)一下靜態(tài)代理和動(dòng)態(tài)代理中的角色:

            靜態(tài)代理:

            Subject:公共接口

            Real Subject:委托類

            Proxy:代理類

            JDK 動(dòng)態(tài)代理:

            Subject:公共接口

            Real Subject:委托類

            Proxy:代理類

            InvocationHandler:處理類,統(tǒng)一調(diào)用方法

            CGLIB 動(dòng)態(tài)代理:

            Subject:公共接口

            Real Subject:委托類

            Proxy:代理類

            MethodInterceptor:方法攔截器,統(tǒng)一調(diào)用方法

            到此,關(guān)于“Java 的代理機(jī)制是什么”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注億速云網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)?lái)更多實(shí)用的文章!

            向AI問(wèn)一下細(xì)節(jié)

            免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

            AI