<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          不懂Nacos沒關系,可以看看它是怎么運用代理模式的

          共 16385字,需瀏覽 33分鐘

           ·

          2022-12-26 09:28

          背景

          看Nacos的源代碼時,發(fā)現(xiàn)其中有對代理模式的運用,而且用得還不錯,可以作為一個典型案例來聊聊,方便大家以更真實的案例來體驗一下代理模式的運用。如果你對Nacos不了解,也并不影響對本篇文章的閱讀和學習。

          本文涉及知識點:代理模式的定義、代理模式的運用場景、Nacos的服務注冊、靜態(tài)代理模式、動態(tài)代理模式、Cglib動態(tài)代理、Spring中AOP所使用的代理等。

          何謂代理模式

          代理模式(Proxy Pattern)是一種結構型設計模式,通常使用代理對象來執(zhí)行目標對象的方法并在代理對象中增強目標對象的方法。

          定義有一些繞口,舉個生活中的簡單例子:你去租房,可以直接找房東,也可以找中介。而代理模式就是你租房不用找房東,通過中介來租,而中介呢,不僅僅能夠提供房屋出租服務(目標對象的方法),還可以提供房屋清潔的服務(對目標對象方法的增強)。

          在上述例子中,中介是代理對象,房東是目標對象(或委托對象),中介為房東提供了出租的功能,在出租的功能上代理又可以提供增強的房屋清潔功能。

          為什么要使用代理模式呢?

          原因有二:

          • 中介隔離作用:在上述例子中,無論是因為客戶嫌直接找房東麻煩,還是房東嫌出租客戶麻煩,中間都需要一個專門的角色來處理這事,它就是代理。也就是說,客戶類不想或者不能直接引用一個委托對象,代理對象就可以在二者之間起到中介的作用。
          • 開閉原則:在上面的例子中,房東只想出租房屋,而租戶租房時還想享受清潔服務,而這個清潔服務就需要通過代理類來處理。這樣不用直接在房東出租功能上修改(新增)清潔服務,僅通過代理類就可以完成,符合開閉原則。上面的例子是提供一些特定的服務,在實踐中,像鑒權、計時、緩存、日志、事務處理等一些公共服務都可以在代理類中完成。

          代理模式的分類

          代理模式通??煞譃閮深悾?strong>靜態(tài)代理和動態(tài)代理。動態(tài)代理的實現(xiàn)又有JDK動態(tài)代理CGLIB動態(tài)代理兩種實現(xiàn)方式。

          靜態(tài)代理是由開發(fā)人員直接編寫代理類,代理類和委托類之間的關系在運行前已經(jīng)確定好的。當需要修改或屏蔽一個或若干類的部分功能,復用另一部分功能時,可使用靜態(tài)代理。

          動態(tài)代理的代理類是在運行時期間由編譯器動態(tài)生成(比如,JVM的反射機制生成代理類),在運行時確定代理類和委托類之間的關系。當需要攔截一批類中的某些方法,在方法前后加入一些公共操作時,可使用動態(tài)代理。

          靜態(tài)代理

          在Nacos中服務注冊接口使用的代理模式為靜態(tài)代理。靜態(tài)代理模式需要先定義接口,委托類和代理類一起實現(xiàn)該接口,然后通過調(diào)用代理類對應的方法間接調(diào)用委托類的對應方法。

          常見的靜態(tài)代理類數(shù)據(jù)模型如下:

          961cc5ef4d7b7c67ed9302b27a34b595.webp靜態(tài)代理(圖片來源網(wǎng)絡)

          上圖中通過代理類對委托類的方法進行拓展,在方法執(zhí)行前后新增一些邏輯處理,比如日志、計時等,這是最簡單的一種代理模式實現(xiàn)。

          在Nacos中靜態(tài)代理模式運用的場景是客戶端實例向Nacos的注冊、注銷等操作。由于實例的注冊方式支持臨時實例和持久實例兩種方式,代理類就起到了判斷到底是采用臨時實例注冊服務,還是使用持久實例注冊服務。

          下面直接以Nacos相關源碼來進行解析說明。

          第一步,定義接口,靜態(tài)代理是需要先定義一個共同的實現(xiàn)接口的。

                public?interface?ClientOperationService?{
          ????
          ????/**
          ?????*?Register?instance?to?service.
          ?????*
          ?????*/
          ????void?registerInstance(Service?service,?Instance?instance,?String?clientId)?throws?NacosException;
          ????
          ????//?...
          }

          在Nacos中定義了一個ClientOperationService的接口,其中提供了實例的注冊、注銷等功能,這里為了方便閱讀,僅展示注冊實例代碼(后續(xù)代碼相同)。

          第二步,定義兩個委托類,一個委托類實現(xiàn)臨時實例注冊,一個委托類實現(xiàn)持久實例注冊。

                @Component("ephemeralClientOperationService")
          public?class?EphemeralClientOperationServiceImpl?implements?ClientOperationService?{
          ????
          ????@Override
          ????public?void?registerInstance(Service?service,?Instance?instance,?String?clientId)?throws?NacosException?{
          ???????//?...?臨時實例注冊邏輯實現(xiàn)
          ????}
          ????//?...
          }

          @Component("persistentClientOperationServiceImpl")
          public?class?PersistentClientOperationServiceImpl?extends?RequestProcessor4CP?implements?ClientOperationService?{
          ????
          ????@Override
          ????public?void?registerInstance(Service?service,?Instance?instance,?String?clientId)?{
          ???????//?...?永久實例注冊邏輯實現(xiàn)
          ????}
          ????//?...
          }????

          EphemeralClientOperationServiceImpl類為臨時實例操作服務實現(xiàn),實現(xiàn)了ClientOperationService接口。PersistentClientOperationServiceImpl類為永久實例操作服務實現(xiàn),同樣實現(xiàn)了ClientOperationService接口。

          第三步,定義代理類。通常情況下,一個代理類代理一個委托類,但在Nacos中,代理類實現(xiàn)了區(qū)分到底是臨時實例還是永久實例的邏輯,因此代理類同時代理了上述兩個委托類。

                @Component
          public?class?ClientOperationServiceProxy?implements?ClientOperationService?{
          ????
          ????private?final?ClientOperationService?ephemeralClientOperationService;
          ????
          ????private?final?ClientOperationService?persistentClientOperationService;
          ????
          ????public?ClientOperationServiceProxy(EphemeralClientOperationServiceImpl?ephemeralClientOperationService,
          ????????????PersistentClientOperationServiceImpl?persistentClientOperationService)?{
          ????????this.ephemeralClientOperationService?=?ephemeralClientOperationService;
          ????????this.persistentClientOperationService?=?persistentClientOperationService;
          ????}
          ????
          ????@Override
          ????public?void?registerInstance(Service?service,?Instance?instance,?String?clientId)?throws?NacosException?{
          ????????final?ClientOperationService?operationService?=?chooseClientOperationService(instance);
          ????????operationService.registerInstance(service,?instance,?clientId);
          ????}
          ????
          ????private?ClientOperationService?chooseClientOperationService(final?Instance?instance)?{
          ????????return?instance.isEphemeral()???ephemeralClientOperationService?:?persistentClientOperationService;
          ????}
          ????//?...
          }

          代理類ClientOperationServiceProxy通過構造方法傳入了兩個委托類,通過chooseClientOperationService方法根據(jù)參數(shù)來判斷具體使用哪個委托類,從而實現(xiàn)了在registerInstance方法中,根據(jù)參數(shù)動態(tài)的判斷注冊實例的方式。

          Nacos的代理模式實現(xiàn),符合我們前面提到的“客戶類不想或者不能直接引用一個委托對象”的場景,這里是(每個)客戶類“不想”每次調(diào)用時都判斷采用何種方式注冊,從而把這個判斷邏輯交給了代理類才進行處理。

          像Nacos中的這種實現(xiàn)就屬于靜態(tài)代理模式,在程序運行之前,已經(jīng)通過代碼實現(xiàn)了具體的代理類實現(xiàn)。靜態(tài)代理的優(yōu)點非常明顯,可以在不改變目標對象的前提下,擴展目標對象的功能。

          但缺點也同樣明顯:

          • 重復性:如果需要代理的業(yè)務或方法越多,則重復的模板代碼就越多;
          • 脆弱性:一旦目標對象(接口)的方法有所變動,比如新增接口,代理對象和目標對象需要同時修改。如果目標對象有多個代理對象,影響范圍可想而知。

          JDK動態(tài)代理

          靜態(tài)代理是在編碼階段已經(jīng)把代理類實現(xiàn)好了,那么是否可以在運行時動態(tài)構建代理類,來實現(xiàn)代理的功能呢?JDK動態(tài)代理便提供了這樣的功能。

          需要注意的是,JDK動態(tài)代理并不等價于動態(tài)代理,它只是動態(tài)代理的實現(xiàn)方式之一,即我們后面要講到的Cglib動態(tài)代理也是動態(tài)代理的實現(xiàn)之一。

          使用JDK動態(tài)代理時,代理對象不需要再實現(xiàn)接口,而目標對象依舊需要實現(xiàn)接口。使用JDK動態(tài)代理時需要用到兩個類:java.lang.reflect.Proxyjava.lang.reflect.InvocationHandler

          下面以用戶登錄時,在登錄操作前后打印日志為例,體驗一下JDK動態(tài)代理的功能。

          第一步,創(chuàng)建業(yè)務接口。

                public?interface?UserService?{
          ?void?login(String?username,?String?password);
          }

          第二步,創(chuàng)建業(yè)務實現(xiàn)類。

                public?class?UserServiceImpl?implements?UserService{

          ?@Override
          ?public?void?login(String?username,?String?password)?{
          ??System.out.println("User?Login?Service!");
          ?}
          }

          第三步,創(chuàng)建業(yè)務邏輯處理器,實現(xiàn)InvocationHandler接口。

                public?class?LogHandler?implements?InvocationHandler?{

          ?/**
          ??*?被代理的對象,實際的方法執(zhí)行者
          ??*/
          ?Object?target;

          ?public?LogHandler(Object?object)?{
          ??this.target?=?object;
          ?}


          ?@Override
          ?public?Object?invoke(Object?proxy,?Method?method,?Object[]?args)?throws?Throwable?{
          ??System.out.println("Before?Login---");
          ??//?調(diào)用target的method方法
          ??Object?result?=?method.invoke(target,?args);
          ??System.out.println("After?Login---");
          ??return?result;
          ?}
          }

          這里我們編寫了一個LogHandler類,實現(xiàn)InvocationHandler接口,重寫invoke方法。

          invoke方法中定義了代理對象調(diào)用方法時希望執(zhí)行的動作,用于集中處理在動態(tài)代理類對象上的方法調(diào)用。

          這里,在執(zhí)行目標類方法前后可添加對應的日志信息打印或其他操作,在上述代碼中分別打印了“Before Login”和“After Login”的信息。

          第四步,模擬客戶端使用。

                public?class?JdkProxyTest?{

          ???public?static?void?main(String[]?args)?{

          ??????//?創(chuàng)建被代理的對象,UserService接口的實現(xiàn)類
          ??????UserServiceImpl?userService?=?new?UserServiceImpl();

          ??????//?創(chuàng)建代理對象,包含三個參數(shù):ClassLoader、目標類實現(xiàn)接口數(shù)組、事件處理器
          ??????UserService?userProxy?=?(UserService)?Proxy.newProxyInstance(userService.getClass().getClassLoader(),
          ????????????userService.getClass().getInterfaces(),
          ????????????new?LogHandler(userService));

          ??????userProxy.login("admin",?"123456");
          ???}
          }

          在上述測試類中,先創(chuàng)建了被代理類的對象,然后通過Proxy的newProxyInstance方法構建了代理對象,生成的代理對象實現(xiàn)了目標類的所有接口,并對接口的方法進行了代理。

          當我們通過代理對象調(diào)用具體方法時,底層將通過反射,調(diào)用我們實現(xiàn)的invoke方法,最后通過調(diào)用目標對象的登錄方法。

          執(zhí)行上述方法,控制臺打印日志如下:

                Before?Login---
          User?Login?Service!
          After?Login---

          可以看到,在登錄操作前后,打印了對應的日志。

          在構建代理對象時,用到了Proxy的newProxyInstance方法,該方法接收三個參數(shù):

          • ClassLoader loader:指定當前目標對象使用類加載器,獲取加載器的方法是固定的。
          • Class<?>[] interfaces:目標對象實現(xiàn)的接口的類型,使用泛型方式確認類型。
          • InvocationHandler h:事件處理,執(zhí)行目標對象的方法時,會觸發(fā)事件處理器的方法,會把當前執(zhí)行目標對象的方法作為參數(shù)傳入。

          通過上述方式,我們實現(xiàn)了基于JDK的動態(tài)代理。JDK動態(tài)代理有以下特點:

          • 通過實現(xiàn)InvocationHandler接口完成代理邏輯,所有函數(shù)調(diào)用都經(jīng)過invoke函數(shù)轉(zhuǎn)發(fā),可在此進行自定義操作,比如日志系統(tǒng)、事務、攔截器、權限控制等。
          • 通過反射代理方法,比較消耗系統(tǒng)性能,但可以減少代理類的數(shù)量,使用更靈活。
          • 代理類必須實現(xiàn)接口

          可以看出,JDK動態(tài)代理的一個致命缺點就是目標類必須實現(xiàn)某個接口。而要解決這個問題,可以通過Cglib代理來實現(xiàn),我們后面會具體講到。

          JDK動態(tài)代理類

          在上述實踐的過程中,我們是否考慮過,通過JDK動態(tài)代理生成的代理類到底是什么樣子呢?我們通過下面的工具類,可以一探究竟。

                public?class?ProxyUtils?{

          ???/**
          ????*?將根據(jù)類信息動態(tài)生成的二進制字節(jié)碼保存到硬盤中,默認的是clazz目錄下
          ????*?params:?clazz?需要生成動態(tài)代理類的類
          ????*?proxyName:?為動態(tài)生成的代理類的名稱
          ????*/
          ???public?static?void?generateClassFile(Class?clazz,?String?proxyName)?{
          ??????//?根據(jù)類信息和提供的代理類名稱,生成字節(jié)碼
          ??????byte[]?classFile?=?ProxyGenerator.generateProxyClass(proxyName,?clazz.getInterfaces());
          ??????String?paths?=?clazz.getResource(".").getPath();
          ??????System.out.println(paths);

          ??????try?(FileOutputStream?out?=?new?FileOutputStream(paths?+?proxyName?+?".class"))?{
          ?????????//保留到硬盤中
          ?????????out.write(classFile);
          ?????????out.flush();
          ??????}?catch?(Exception?e)?{
          ?????????e.printStackTrace();
          ??????}
          ???}
          }

          上面代碼定義了一個將代理類保持到磁盤中的工具類。然后,在JdkProxyTest類的最后,調(diào)用該方法,將JDK動態(tài)生成的代理類打印出來。

                public?class?JdkProxyTest?{

          ?public?static?void?main(String[]?args)?{

          ??//?創(chuàng)建被代理的對象,UserService接口的實現(xiàn)類
          ??UserServiceImpl?userService?=?new?UserServiceImpl();

          ??//?創(chuàng)建代理對象,包含三個參數(shù):ClassLoader、目標類實現(xiàn)接口數(shù)組、事件處理器
          ??UserService?userProxy?=?(UserService)?Proxy.newProxyInstance(userService.getClass().getClassLoader(),
          ????userService.getClass().getInterfaces(),
          ????new?LogHandler(userService));

          ??userProxy.login("admin",?"123456");

          ??//?保存JDK動態(tài)代理生成的代理類,類名保存為?UserServiceProxy
          ??ProxyUtils.generateClassFile(userService.getClass(),?"UserServiceProxy");

          ?}
          }

          其他代碼未變,最后一行添加了工具類ProxyUtils的調(diào)用。

          執(zhí)行上述代碼,會在項目目錄的target下生成名為“UserServiceProxy”的class文件。本人執(zhí)行時,打印的路徑為“.../target/classes/com/secbro2/proxy/”。

          在該目錄下找到UserServiceProxy.class類文件,通過IDE的反編譯功能,可看到如下代碼:

                public?final?class?UserServiceProxy?extends?Proxy?implements?UserService?{
          ????private?static?Method?m1;
          ????private?static?Method?m2;
          ????private?static?Method?m3;
          ????private?static?Method?m0;

          ????public?UserServiceProxy(InvocationHandler?var1)?throws??{
          ????????super(var1);
          ????}

          ????public?final?boolean?equals(Object?var1)?throws??{
          ????????try?{
          ????????????return?(Boolean)super.h.invoke(this,?m1,?new?Object[]{var1});
          ????????}?catch?(RuntimeException?|?Error?var3)?{
          ????????????throw?var3;
          ????????}?catch?(Throwable?var4)?{
          ????????????throw?new?UndeclaredThrowableException(var4);
          ????????}
          ????}

          ????public?final?String?toString()?throws??{
          ????????try?{
          ????????????return?(String)super.h.invoke(this,?m2,?(Object[])null);
          ????????}?catch?(RuntimeException?|?Error?var2)?{
          ????????????throw?var2;
          ????????}?catch?(Throwable?var3)?{
          ????????????throw?new?UndeclaredThrowableException(var3);
          ????????}
          ????}

          ????public?final?void?login(String?var1,?String?var2)?throws??{
          ????????try?{
          ????????????super.h.invoke(this,?m3,?new?Object[]{var1,?var2});
          ????????}?catch?(RuntimeException?|?Error?var4)?{
          ????????????throw?var4;
          ????????}?catch?(Throwable?var5)?{
          ????????????throw?new?UndeclaredThrowableException(var5);
          ????????}
          ????}

          ????public?final?int?hashCode()?throws??{
          ????????try?{
          ????????????return?(Integer)super.h.invoke(this,?m0,?(Object[])null);
          ????????}?catch?(RuntimeException?|?Error?var2)?{
          ????????????throw?var2;
          ????????}?catch?(Throwable?var3)?{
          ????????????throw?new?UndeclaredThrowableException(var3);
          ????????}
          ????}

          ????static?{
          ????????try?{
          ????????????m1?=?Class.forName("java.lang.Object").getMethod("equals",?Class.forName("java.lang.Object"));
          ????????????m2?=?Class.forName("java.lang.Object").getMethod("toString");
          ????????????m3?=?Class.forName("com.secbro2.proxy.UserService").getMethod("login",?Class.forName("java.lang.String"),?Class.forName("java.lang.String"));
          ????????????m0?=?Class.forName("java.lang.Object").getMethod("hashCode");
          ????????}?catch?(NoSuchMethodException?var2)?{
          ????????????throw?new?NoSuchMethodError(var2.getMessage());
          ????????}?catch?(ClassNotFoundException?var3)?{
          ????????????throw?new?NoClassDefFoundError(var3.getMessage());
          ????????}
          ????}
          }

          從反編譯的代理類中,我們可以得到以下信息:

          • UserServiceProxy繼承了Proxy類,實現(xiàn)了UserService接口,當然接口中定義的login方法也同樣實現(xiàn)了。同時,還實現(xiàn)了equals、hashCode、toString等方法。
          • 由于UserServiceProxy繼承了Proxy類,所以每個代理類都會關聯(lián)一個InvocationHandler方法調(diào)用處理器。
          • 類和所有方法都被 public final 修飾,所以代理類只可被使用,不可以再被繼承。
          • 每個方法都有一個 Method對象來描述,Method對象在static靜態(tài)代碼塊中創(chuàng)建,以 m + 數(shù)字 的格式命名。
          • 調(diào)用方法時通過 super.h.invoke(this, m1, (Object[])null); 調(diào)用,其中的 super.h.invoke 實際上是在創(chuàng)建代理時傳遞給 Proxy.newProxyInstance 的LogHandler對象,它繼承InvocationHandler類,負責實際的調(diào)用處理邏輯。

          而LogHandler的 invoke 方法接收到method、args 等參數(shù)后,進行一些處理,然后通過反射讓被代理的對象 target 執(zhí)行方法。

          至此,我們已經(jīng)了解了基于JDK動態(tài)代理的使用以及所生成代理類的結構,下面就來看看無需目標類實現(xiàn)接口的Cglib動態(tài)代理實現(xiàn)。

          Cglib動態(tài)代理

          在上面的實例中可以看到無論使用靜態(tài)代理或是JDK動態(tài)代理,目標類都需要實現(xiàn)一個接口。在某些情況下,目標類可能并沒有實現(xiàn)接口,這時就可以使用Cglib動態(tài)代理。

          Cglib(Code Generation Library)是一個功能強大、高性能、開源的代碼生成包,它可以為沒有實現(xiàn)接口的類提供代理。

          Cglib代理可以稱為子類代理,具體而言,Cglib會在內(nèi)存中構建一個目標類的子類,重寫其業(yè)務方法,從而實現(xiàn)對目標對象功能的擴展。因為采用繼承機制,所以不能對final修飾的類進行代理。

          Cglib通過Enhancer類來生成代理類,通過實現(xiàn)MethodInterceptor接口,在其intercept方法中對目標對象的方法進行增強,并可通過Method或MethodProxy繼承類來調(diào)用原有方法。

          這次以下訂單(OrderService)為例來展示一下通過Cglib在下訂單操作前后添加日志信息。

          在使用Cglib之前,首先需要引入對應的依賴jar包,大多數(shù)項目中往往Cglib已經(jīng)被間接引入了,可核實其版本是否是預期版本。這里采用Maven形式,引入Cglib依賴。

                <dependency>
          ?????<groupId>cglib</groupId>
          ?????<artifactId>cglib</artifactId>
          ?????<version>3.1</version>
          </dependency>

          第一步,定義業(yè)務類OrderService,不需要實現(xiàn)任何接口。

                public?class?OrderService?{
          ?public?void?order(String?orderNo){
          ??System.out.println("order?something...?");
          ?}
          }

          第二步,定義動態(tài)代理類的創(chuàng)建及業(yè)務實現(xiàn)。

                /**
          ?*?動態(tài)代理類,實現(xiàn)方法攔截器接口
          ?**/
          public?class?LogInterceptor?implements?MethodInterceptor?{

          ?/**
          ??*?給目標對象創(chuàng)建一個代理對象
          ??*/
          ?public?Object?getProxyInstance(Class?targetClass){
          ??//?1.工具類
          ??Enhancer?enhancer?=?new?Enhancer();
          ??//?2.設置父類
          ??enhancer.setSuperclass(targetClass);
          ??//?3.設置回調(diào)函數(shù)
          ??enhancer.setCallback(this);
          ??//?4.創(chuàng)建子類(代理對象)
          ??return?enhancer.create();
          ??//?上述方法也可以直接使用如下代碼替代
          ??//?return?Enhancer.create(targetClass,this);
          ?}

          ?/**
          ??*
          ??*?@param?o?要進行增強的對象
          ??*?@param?method?攔截的方法
          ??*?@param?objects?方法參數(shù)列表(數(shù)組)
          ??*?@param?methodProxy?方法的代理,invokeSuper方法表示對被代理對象方法的調(diào)用
          ??*/
          ?@Override
          ?public?Object?intercept(Object?o,?Method?method,?Object[]?objects,?MethodProxy?methodProxy)?throws?Throwable?{
          ??//?擴展日志記錄
          ??System.out.println("LogInterceptor:Before Login---");
          ??//?注意:調(diào)用的invokeSuper而不是invoke,否則死循環(huán)。
          ??//?methodProxy.invokeSuper執(zhí)行的是原始類的方法,method.invoke執(zhí)行的是子類的方法
          ??Object?object?=?methodProxy.invokeSuper(o,?objects);
          ??//?擴展日志記錄
          ??System.out.println("LogInterceptor:After Login---");
          ??return?object;
          ?}
          }

          LogInterceptor類實現(xiàn)了MethodInterceptor接口,在重寫的intercept方法中添加了要擴展的業(yè)務內(nèi)邏輯。其中需要注意的是,intercept方法內(nèi)調(diào)用的是MethodProxy#invokeSuper方法,而不是invoke方法。

          同時,在LogInterceptor類中定義了創(chuàng)建目標對象的代理對象的工具方法getProxyInstance,值得留意的是Enhancer#setCallback方法的參數(shù)this,指的便是LogInterceptor的當前對象。

          第三步,編寫測試客戶端。

                public?class?CglibTest?{

          ?public?static?void?main(String[]?args)?{
          ??OrderService?orderService?=?(OrderService)?new?LogInterceptor().getProxyInstance(OrderService.class);
          ??orderService.order("123");
          ?}
          }

          執(zhí)行上述方法,打印日志如下:

                LogInterceptor:Before Login---
          order?something...?
          LogInterceptor:After Login---

          成功的在目標對象的方法前后植入日志信息。

          關于Cglib動態(tài)代理有以下特點:

          • 需要引入Cglib的依賴jar包,通常Spring的核心包已包含Cglib功能。
          • Cglib動態(tài)代理不需要接口信息,但是它攔截并包裝被代理類的所有方法。
          • 委托類不能為final,否則報錯java.lang.IllegalArgumentException: Cannot subclass final class xxx。
          • 不會攔截委托類中無法重載的final/static方法,而是跳過此類方法只代理其他方法。
          • 實現(xiàn) MethodInterceptor接口,用來處理對代理類上所有方法的請求。

          三種代理對比

          靜態(tài)代理:代理類和目標類都需要實現(xiàn)接口,從而達到代理增強其功能。

          JDK動態(tài)代理:基于Java反射機制實現(xiàn),目標類必須實現(xiàn)接口才能生成代理對象。使用Proxy.newProxyInstance方法生成代理類,并實現(xiàn)InvocationHandler中的invoke方法,實現(xiàn)增強功能。

          Cglib動態(tài)代理:基于ASM機制實現(xiàn),通過生成目標類的子類作為代理類。無需實現(xiàn)接口,使用Cblib中的Enhancer來生成代理對象子類,并實現(xiàn)MethodInterceptorintercept方法來實現(xiàn)增強功能。

          JDK動態(tài)代理的優(yōu)勢:JDK自身支持,減少依賴,可隨著JDK平滑升級,代碼實現(xiàn)簡單。

          Cglib動態(tài)代理的優(yōu)勢:無需實現(xiàn)接口,達到無侵入;只操作我們關心的類,而不必為其他相關類增加工作量;

          Spring中動態(tài)代理支持

          Spring的AOP實現(xiàn)中主要應用了JDK動態(tài)代理以及Cglib動態(tài)代理,對應的實現(xiàn)類位于spring-aop的jar包中。

                //?基于JDK的動態(tài)代理實現(xiàn)類
          org.springframework.aop.framework.JdkDynamicAopProxy
          //?基于Cglib的動態(tài)代理實現(xiàn)類
          org.springframework.aop.framework.CglibAopProxy

          Spring默認使用JDK動態(tài)代理實現(xiàn)AOP,類如果實現(xiàn)了接口,Spring就會使用這種方式的動態(tài)代理。如果目標對象沒有實現(xiàn)接口,則需要使用Cglib動態(tài)代理來實現(xiàn)。

          在了解了JDK動態(tài)代理及Cglib動態(tài)代理的使用及特性之后,大家可以對照思考一下Spring事務失效的一些場景,Spring的事務實現(xiàn)便是基于AOP來實現(xiàn)的,比如:

          • 方法使用private定義,導致事務失效:被代理方法必須是public。
          • 方法使用final修飾:如果方法被定義為final,JDK動態(tài)代理或Cglib無法重寫該方法。
          • 同一類內(nèi)部方法調(diào)用:直接使用this對象調(diào)用方法,無法生成代理方法,會導致事務失效。

          關于Spring中動態(tài)代理的其他內(nèi)容,本文就不再展開了,感興趣的讀者可直接閱讀對應的源碼。

          小結

          本文從Nacos中的靜態(tài)代理模式實現(xiàn),延伸拓展講解了代理模式的定義、代理模式的運用場景、靜態(tài)代理模式、動態(tài)代理模式、Cglib動態(tài)代理、Spring中AOP所使用的代理等。

          通過文章中關聯(lián)的知識點,以及在不同跨度的項目中的實踐案例,大家應該能夠感知到到代理模式,特別是基于JDK動態(tài)代理和Cglib動態(tài)代理在實踐中的重要性。抓緊學一波吧。

          參考文章:

          https://segmentfault.com/a/1190000040407024

          https://juejin.cn/post/6844903744954433544

          https://www.cnblogs.com/clover-toeic/p/11715583.html



          如果你覺得這篇文 章不錯,那么,下篇通常會更好。 備注“公眾號”添加微信好友 (微信號:zhuan2quan) 。

          ▲? 按關 注”程序 新視界“,洞察技術內(nèi)幕


          瀏覽 55
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  天天肏天天射 | 18XXX亚洲HD护士JD | 国产主播91 | www.黄色电影 | 成人黄片免费网站 |