偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

不懂Nacos沒關(guān)系,可以看看它是怎么運(yùn)用代理模式的

開發(fā) 前端
本文涉及知識點(diǎn):代理模式的定義、代理模式的運(yùn)用場景、Nacos的服務(wù)注冊、靜態(tài)代理模式、動態(tài)代理模式、Cglib動態(tài)代理、Spring中AOP所使用的代理等。

?背景

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

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

何謂代理模式

代理模式(Proxy Pattern)是一種結(jié)構(gòu)型設(shè)計(jì)模式,通常使用代理對象來執(zhí)行目標(biāo)對象的方法并在代理對象中增強(qiáng)目標(biāo)對象的方法。

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

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

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

原因有二:

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

代理模式的分類

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

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

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

靜態(tài)代理

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

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

圖片

靜態(tài)代理(圖片來源網(wǎng)絡(luò))

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

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

下面直接以Nacos相關(guān)源碼來進(jìn)行解析說明。

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

public interface ClientOperationService {

/**
* Register instance to service.
*
*/
void registerInstance(Service service, Instance instance, String clientId) throws NacosException;

// ...
}

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

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

@Component("ephemeralClientOperationService")
public class EphemeralClientOperationServiceImpl implements ClientOperationService {

@Override
public void registerInstance(Service service, Instance instance, String clientId) throws NacosException {
// ... 臨時實(shí)例注冊邏輯實(shí)現(xiàn)
}
// ...
}

@Component("persistentClientOperationServiceImpl")
public class PersistentClientOperationServiceImpl extends RequestProcessor4CP implements ClientOperationService {

@Override
public void registerInstance(Service service, Instance instance, String clientId) {
// ... 永久實(shí)例注冊邏輯實(shí)現(xiàn)
}
// ...
}

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

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

@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?通過構(gòu)造方法傳入了兩個委托類,通過chooseClientOperationService?方法根據(jù)參數(shù)來判斷具體使用哪個委托類,從而實(shí)現(xiàn)了在registerInstance方法中,根據(jù)參數(shù)動態(tài)的判斷注冊實(shí)例的方式。

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

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

但缺點(diǎn)也同樣明顯:

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

JDK動態(tài)代理

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

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

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

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

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

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

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

public class UserServiceImpl implements UserService{

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

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

public class LogHandler implements InvocationHandler {

/**
* 被代理的對象,實(shí)際的方法執(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類,實(shí)現(xiàn)InvocationHandler接口,重寫invoke方法。

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

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

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

public class JdkProxyTest {

public static void main(String[] args) {

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

// 創(chuàng)建代理對象,包含三個參數(shù):ClassLoader、目標(biāo)類實(shí)現(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方法構(gòu)建了代理對象,生成的代理對象實(shí)現(xiàn)了目標(biāo)類的所有接口,并對接口的方法進(jìn)行了代理。

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

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

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

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

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

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

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

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

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

JDK動態(tài)代理類

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

public class ProxyUtils {

/**
* 將根據(jù)類信息動態(tài)生成的二進(jìn)制字節(jié)碼保存到硬盤中,默認(rèn)的是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接口的實(shí)現(xiàn)類
UserServiceImpl userService = new UserServiceImpl();

// 創(chuàng)建代理對象,包含三個參數(shù):ClassLoader、目標(biāo)類實(shí)現(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í)行上述代碼,會在項(xiàng)目目錄的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類,實(shí)現(xiàn)了UserService接口,當(dāng)然接口中定義的login方法也同樣實(shí)現(xiàn)了。同時,還實(shí)現(xiàn)了equals、hashCode、toString等方法。
  • 由于UserServiceProxy繼承了Proxy類,所以每個代理類都會關(guān)聯(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? 實(shí)際上是在創(chuàng)建代理時傳遞給 Proxy.newProxyInstance 的LogHandler對象,它繼承InvocationHandler類,負(fù)責(zé)實(shí)際的調(diào)用處理邏輯。
  • 而LogHandler的 invoke 方法接收到method、args 等參數(shù)后,進(jìn)行一些處理,然后通過反射讓被代理的對象 target 執(zhí)行方法。

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

Cglib動態(tài)代理

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

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

Cglib代理可以稱為子類代理,具體而言,Cglib會在內(nèi)存中構(gòu)建一個目標(biāo)類的子類,重寫其業(yè)務(wù)方法,從而實(shí)現(xiàn)對目標(biāo)對象功能的擴(kuò)展。因?yàn)椴捎美^承機(jī)制,所以不能對final修飾的類進(jìn)行代理。

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

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

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

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

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

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

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

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

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

/**
*
* @param o 要進(jìn)行增強(qiáng)的對象
* @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 {
// 擴(kuò)展日志記錄
System.out.println("LogInterceptor:Before Login---");
// 注意:調(diào)用的invokeSuper而不是invoke,否則死循環(huán)。
// methodProxy.invokeSuper執(zhí)行的是原始類的方法,method.invoke執(zhí)行的是子類的方法
Object object = methodProxy.invokeSuper(o, objects);
// 擴(kuò)展日志記錄
System.out.println("LogInterceptor:After Login---");
return object;
}
}

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

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

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

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---

成功的在目標(biāo)對象的方法前后植入日志信息。

關(guān)于Cglib動態(tài)代理有以下特點(diǎn):

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

三種代理對比

靜態(tài)代理:代理類和目標(biāo)類都需要實(shí)現(xiàn)接口,從而達(dá)到代理增強(qiáng)其功能。

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

Cglib動態(tài)代理:基于ASM機(jī)制實(shí)現(xiàn),通過生成目標(biāo)類的子類作為代理類。無需實(shí)現(xiàn)接口,使用Cblib?中的Enhancer?來生成代理對象子類,并實(shí)現(xiàn)MethodInterceptor的intercept方法來實(shí)現(xiàn)增強(qiáng)功能。

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

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

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

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

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

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

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

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

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

小結(jié)

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

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

參考文章:

https://segmentfault.com/a/1190000040407024

https://juejin.cn/post/6844903744954433544

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

責(zé)任編輯:武曉燕 來源: 程序新視界
相關(guān)推薦

2016-12-15 12:24:03

Oracle數(shù)據(jù)庫密碼

2019-12-27 15:18:01

微軟

2009-07-02 18:53:07

Linux

2011-09-06 14:44:35

Smilebox照片

2024-04-01 06:21:10

2015-06-09 03:19:57

WWDC服務(wù)

2020-11-16 15:53:51

物聯(lián)網(wǎng)IIoT數(shù)據(jù)

2023-03-29 10:04:18

圖像AI

2017-08-17 10:30:49

惠普ENVY 13首發(fā)價

2012-10-25 13:14:36

2016-12-07 14:24:12

數(shù)據(jù)數(shù)據(jù)思維

2021-10-27 15:57:48

機(jī)器學(xué)習(xí)人工智能計(jì)算機(jī)

2021-10-29 14:45:42

計(jì)算數(shù)據(jù) 技術(shù)

2019-08-20 09:26:48

AI人工智能麻省理工學(xué)院

2021-07-28 06:51:08

Nacos代理模式

2015-10-16 18:02:03

互聯(lián)網(wǎng)盤點(diǎn)

2020-12-02 10:13:03

AI 數(shù)據(jù)人工智能

2024-03-25 15:04:03

AI數(shù)據(jù)

2021-03-10 08:16:06

Nacos集群搭建微服務(wù)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號