2023年再不會動態(tài)代理,就要被淘汰了
代理模式
一、引言
在 Spring
中,最重要的應(yīng)該當(dāng)屬 IOC
和 AOP
了,IOC
的源碼流程還比較簡單,但 AOP
的流程就較為抽象了。
其中,AOP
中代理模式的重要性不言而喻,但對于沒了解過代理模式的人來說,痛苦至極
于是,我就去看了動態(tài)代理的實現(xiàn),發(fā)現(xiàn)網(wǎng)上大多數(shù)文章講的都是不清不楚,甚至講了和沒講似的,讓我極其難受
本著咱們方向主打的就是源碼,直接從從源碼角度講述一下 代理模式
兄弟們系好安全帶,準(zhǔn)備發(fā)車!
注意:本文篇幅較長,請留出較長時間來閱讀
二、定義
代理模式的定義:由于某些原因需要給某對象提供一個代理以控制對該對象的訪問。這時,訪問對象不適合或者不能直接引用目標(biāo)對象,代理對象作為訪問對象和目標(biāo)對象之間的中介。
舉個生活中常見的例子:客戶想買房,房東有很多房,提供賣房服務(wù),但房東不會帶客戶看房,于是客戶通過中介買房。
這時候?qū)τ诜繓|來說,不直接和客戶溝通,而是交于中介進(jìn)行代理
對于中介來說,她也會在原有的基礎(chǔ)上收取一定的中介費(fèi)
三、靜態(tài)代理
我們創(chuàng)建 Landlord
接口如下:
java復(fù)制代碼public interface Landlord { ?? ?// 出租房子 ?? ?void apartmentToRent(); }
創(chuàng)建其實現(xiàn)類 HangZhouLandlord
代表杭州房東出租房子
java復(fù)制代碼public class HangZhouLandlord implements Landlord { ?? ?@Override ?? ?public void apartmentToRent() { ?? ? ? ?System.out.println("杭州房東出租房子"); ?? ?} }
創(chuàng)建代理類 LandlordProxy
,代表中介服務(wù)
java復(fù)制代碼public class LandlordProxy { ? ? ?public Landlord landlord; ? ? ?public LandlordProxy(Landlord landlord) { ?? ? ? ?this.landlord = landlord; ?? ?} ? ? ?public void apartmentToRent() { ?? ? ? ?apartmentToRentBefore(); ?? ? ? ?landlord.apartmentToRent(); ?? ? ? ?apartmentToRentAfter(); ?? ?} ? ? ?public void apartmentToRentBefore() { ?? ? ? ?System.out.println("出租房前,收取中介費(fèi)"); ?? ?} ? ? ?public void apartmentToRentAfter() { ?? ? ? ?System.out.println("出租房后,簽訂合同"); ?? ?} }
創(chuàng)建最終測試:
java復(fù)制代碼public class JavaMain { ?? ?public static void main(String[] args) { ?? ? ? ?Landlord landlord = new HangZhouLandlord(); ? ? ? ? ?LandlordProxy proxy = new LandlordProxy(landlord); ?? // 從中介進(jìn)行租房 ?? ? ? ?proxy.apartmentToRent(); ?? ?} }
得出最終結(jié)果:
json復(fù)制代碼出租房前,收取中介費(fèi) 杭州房東出租房子 出租房后,簽訂合同
通過上述 demo
我們大概了解代理模式是怎么一回事
優(yōu)點:
在不修改目標(biāo)對象的功能前提下,能通過代理對象對目標(biāo)功能擴(kuò)展
缺點:
代理對象需要與目標(biāo)對象實現(xiàn)一樣的接口,所以會有很多代理類,一旦接口增加方法,目標(biāo)對象與代理對象都要維護(hù)
四、動態(tài)代理
動態(tài)代理利用了JDK API,動態(tài)地在內(nèi)存中構(gòu)建代理對象,從而實現(xiàn)對目標(biāo)對象的代理功能,動態(tài)代理又被稱為JDK代理或接口代理。
靜態(tài)代理與動態(tài)代理的區(qū)別:
靜態(tài)代理在編譯時就已經(jīng)實現(xiàn)了,編譯完成后代理類是一個實際的
class
文件動態(tài)代理是在運(yùn)行時動態(tài)生成的,即編譯完成后沒有實際的
class
文件,而是在運(yùn)行時動態(tài)生成類字節(jié)碼,并加載到JVM
中
1、JDK代理
代碼如下:
java復(fù)制代碼public class ProxyFactory { ?? ?// 目標(biāo)方法 ?? ?public Object target; ?? ?public ProxyFactory(Object target) { ?? ? ? ?this.target = target; ?? ?} ? ? ?public Object getProxyInstance() { ?? ? ? ?return Proxy.newProxyInstance( ?? ? ? ? ? ? ? ?// 目標(biāo)對象的類加載器 ?? ? ? ? ? ? ? ?target.getClass().getClassLoader(), ?? ? ? ? ? ? ? ?// 目標(biāo)對象的接口類型 ?? ? ? ? ? ? ? ?target.getClass().getInterfaces(), ?? ? ? ? ? ? ? ?// 事件處理器 ?? ? ? ? ? ? ? ?new InvocationHandler() { ?? ? ? ? ? ? ? ? ? ?/** ?? ? ? ? ? ? ? ? ? ? * ?? ? ? ? ? ? ? ? ? ? * @param proxy ?代理對象 ?? ? ? ? ? ? ? ? ? ? * @param method 代理對象調(diào)用的方法 ?? ? ? ? ? ? ? ? ? ? * @param args ? 代理對象調(diào)用方法時實際的參數(shù) ?? ? ? ? ? ? ? ? ? ? * @return ?? ? ? ? ? ? ? ? ? ? * @throws Throwable ?? ? ? ? ? ? ? ? ? ? */ ?? ? ? ? ? ? ? ? ? ?@Override ?? ? ? ? ? ? ? ? ? ?public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { ?? ? ? ? ? ? ? ? ? ? ? ?System.out.println("我是前置增強(qiáng)"); ?? ? ? ? ? ? ? ? ? ? ? ?method.invoke(target, args); ?? ? ? ? ? ? ? ? ? ? ? ?System.out.println("我是后置增強(qiáng)"); ?? ? ? ? ? ? ? ? ? ? ? ?return null; ?? ? ? ? ? ? ? ? ? ?} ?? ? ? ? ? ? ? ?} ?? ? ? ?); ?? ?} }
我們測試一下:
java復(fù)制代碼public class JavaMain { ?? ?public static void main(String[] args) { ?? ? ? ?Landlord landlord = new HangZhouLandlord(); ? ? ? ? ?System.out.println(landlord.getClass()); ? ? ? ? ?Landlord proxy = (Landlord) new ProxyFactory(landlord).getProxyInstance(); ? ? ? ? ?proxy.apartmentToRent(); ? ? ? ? ?System.out.println(proxy.getClass()); ? ?? ? ? ?while (true){} ?? ?} }
得出結(jié)果:
json復(fù)制代碼class com.company.proxy.HangZhouLandlord 我是前置增強(qiáng) 杭州房東出租房子 我是后置增強(qiáng) class com.sun.proxy.$Proxy0
這里可能有小伙伴已經(jīng)懵了,接著往后看
1.1 JDK類的動態(tài)生成
Java虛擬機(jī)類加載過程主要分為五個階段:加載、驗證、準(zhǔn)備、解析、初始化。其中加載階段需要完成以下3件事情:
通過一個類的全限定名來獲取定義此類的二進(jìn)制字節(jié)流
將這個字節(jié)流所代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時數(shù)據(jù)結(jié)構(gòu)
在內(nèi)存中生成一個代表這個類的
java.lang.Class
對象,作為方法區(qū)這個類的各種數(shù)據(jù)訪問入口
由于虛擬機(jī)規(guī)范對這3點要求并不具體,所以實際的實現(xiàn)是非常靈活的,關(guān)于第1點,獲取類的二進(jìn)制字節(jié)流(class字節(jié)碼)就有很多途徑:
從本地獲取
從網(wǎng)絡(luò)中獲取
運(yùn)行時計算生成,這種場景使用最多的是動態(tài)代理技術(shù),在
java.lang.reflect.Proxy
類中,就是用了ProxyGenerator.generateProxyClass
來為特定接口生成形式為*$Proxy
的代理類的二進(jìn)制字節(jié)流
所以,動態(tài)代理就是想辦法,根據(jù)接口或目標(biāo)對象,計算出代理類的字節(jié)碼,然后再加載到
JVM
中使用
1.2 JDK動態(tài)代理流程
所以,我們可以得出一個結(jié)論:我們上面的 $Proxy0
實際上是 JVM
在編譯時期加載出來的類,由于這個類是編譯時期加載的,所以我們沒辦法在 IDEA
里面看到。
可能一般的文章,到這里基本就結(jié)束了,讓大家知道 $Proxy0
是由 JVM
編譯時期加載出來的類
但大家都知道,小黃的文章主打的就是一個硬核、源碼級。所以,我們直接去看 $Proxy0
的源代碼
首先,我們需要下載一個 arthas
的產(chǎn)品,網(wǎng)址:arthas.aliyun.com/doc/,跟隨流程解壓…
Arthas 是一款線上監(jiān)控診斷產(chǎn)品,通過全局視角實時查看應(yīng)用 load、內(nèi)存、gc、線程的狀態(tài)信息,并能在不修改應(yīng)用代碼的情況下,對業(yè)務(wù)問題進(jìn)行診斷,包括查看方法調(diào)用的出入?yún)?、異常,監(jiān)測方法執(zhí)行耗時,類加載信息等,大大提升線上問題排查效率。
當(dāng)我們一切準(zhǔn)備完成后,啟動我們上面動態(tài)代理的測試 JavaMain
類
啟動完成后,進(jìn)入我們的 arthas
頁面,執(zhí)行命令:java -jar arthas-boot.jar
我們可以看到,我們的目標(biāo)類 com.company.proxy.JavaMain
就出現(xiàn)了,隨后我們按下 4
,進(jìn)入到我們的監(jiān)控頁面。
隨后使用 jad com.sun.proxy.$Proxy0
之后,可以看到我們已經(jīng)解析出來 $Proxy0
的源碼了
我們將其復(fù)制到下面,并刪減一些不必要的信息。
java復(fù)制代碼public final class $Proxy0 extends Proxy implements Landlord { ?? ?private static Method m3; ? ?? ?// $Proxy0 類的構(gòu)造方法 ?? ?// 參數(shù)為 invocationHandler ?? ?public $Proxy0(InvocationHandler invocationHandler) { ?? ? ? ?super(invocationHandler); ?? ?} ? ? ?static { ?? ? ? ?m3 = Class.forName("com.company.proxy.Landlord").getMethod("apartmentToRent", new Class[0]); ?? ?} ? ? ?public final void apartmentToRent() { ?? ? ? ?this.h.invoke(this, m3, null); ?? ? ? ?return; ?? ?} }
我們先看其有參構(gòu)造方法,可以看到 $Proxy0
的構(gòu)造方法入?yún)?InvocationHandler
,有沒有感覺似曾相識。
如果你這里忘掉了,不妨去看一下動態(tài)代理的 ProxyFactory
的代碼,可以發(fā)現(xiàn),我們 Proxy.newProxyInstance()
的第三個自定義的參數(shù),也正是我們的 InvocationHandler
。
我們猜測一下,如果這里的傳的 InvocationHandler
是我們之前自定義的 InvocationHandler
那么,如果我調(diào)用 $Proxy0.apartmentToRent()
是不是就是執(zhí)行下面的代碼:
java復(fù)制代碼public final void apartmentToRent() { ?? ?this.h.invoke(this, m3, null); ?? ?return; } ?// 這里的h.invoke執(zhí)行的是我們這里自定義的方法,然后進(jìn)行的前后增強(qiáng) public Object getProxyInstance() { ?? ? ? ?return Proxy.newProxyInstance( ?? ? ? ? ? ? ? ?target.getClass().getClassLoader(), ?? ? ? ? ? ? ? ?target.getClass().getInterfaces(), ?? ? ? ? ? ? ? ?new InvocationHandler() { ?? ? ? ? ? ? ? ? ? ?@Override ?? ? ? ? ? ? ? ? ? ?public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { ?? ? ? ? ? ? ? ? ? ? ? ?System.out.println("我是前置增強(qiáng)"); ?? ? ? ? ? ? ? ? ? ? ? ?method.invoke(target, args); ?? ? ? ? ? ? ? ? ? ? ? ?System.out.println("我是后置增強(qiáng)"); ?? ? ? ? ? ? ? ? ? ? ? ?return null; ?? ? ? ? ? ? ? ? ? ?} ?? ? ? ? ? ? ? ?} ?? ? ? ?);
如果說我們這個猜測是正確的話,那么會得出這樣的幾個結(jié)論:
我們的代理類實際上是實現(xiàn)了
Landlord
的接口,然后重寫了Landlord
接口中的apartmentToRent
方法當(dāng)外界調(diào)用代理類的
apartmentToRent()
方法時,實際上是調(diào)用的我們自定義的new InvocationHandler()
類里面的invoke
方法
還有我們的最后一步,也就是證明 $Proxy0
的構(gòu)造入?yún)?InvocationHandler
就是我們自定義的 InvocationHandler
,廢話不多說,直接來看代理的源碼。
java復(fù)制代碼return Proxy.newProxyInstance(ClassLoader,Interfaces,new InvocationHandler() {}); public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h){ ?? ?// cl = class com.sun.proxy.$Proxy0 ?? ?Class<?> cl = getProxyClass0(loader, intfs); ?? ?// cons = public com.sun.proxy.$Proxy0(java.lang.reflect.InvocationHandler) ?? ?final Constructor<?> cons = cl.getConstructor(constructorParams); ?? ?// 根據(jù)構(gòu)造參數(shù)實例化對象 ?? ?return cons.newInstance(new Object[]{h}); }
我們通過源碼可以看到,一共分為三步(下面為反射的內(nèi)容,如不熟悉可提前學(xué)習(xí)下反射):
拿到
$Proxy0
的Class
根據(jù)
Class
拿到其構(gòu)造方法根據(jù)構(gòu)造方法傳入?yún)?shù)進(jìn)行實例化
這就確定了我們上述的猜想是正確的。
2、Cglib代理
cglib (Code Generation Library ) 是一個第三方代碼生成類庫,運(yùn)行時在內(nèi)存中動態(tài)生成一個子類對象從而實現(xiàn)對目標(biāo)對象功能的擴(kuò)展。cglib
為沒有實現(xiàn)接口的類提供代理,為 JDK
的動態(tài)代理提供了很好的補(bǔ)充。
最底層是字節(jié)碼
ASM
是操作字節(jié)碼的工具cglib
基于ASM
字節(jié)碼工具操作字節(jié)碼(即動態(tài)生成代理,對方法進(jìn)行增強(qiáng))SpringAOP
基于cglib
進(jìn)行封裝,實現(xiàn)cglib
方式的動態(tài)代理
使用 cglib
需要引入 cglib
的jar包,如果你已經(jīng)有 spring-core
的jar包,則無需引入,因為 spring
中包含了cglib
。
xml復(fù)制代碼cglib
的Maven坐標(biāo)<dependency> ?? ?<groupId>cglib</groupId> ?? ?<artifactId>cglib</artifactId> ?? ?<version>3.2.5</version> </dependency>
2.1 cglib動態(tài)代理實現(xiàn)
還是同樣的配方,我們要創(chuàng)建一個需要代理的類(UserServiceImpl),但不需要實現(xiàn)任何的接口,因為我們的 cglib
是根據(jù)類來進(jìn)行創(chuàng)建的。
UserServiceImpl
java復(fù)制代碼public class UserServiceImpl { ?? ?// 查詢功能 ?? ?List<String> findUserList() { ?? ? ? ?return Collections.singletonList("小A"); ?? ?} }
實現(xiàn) cglib
的工廠類:UserLogProxy
java復(fù)制代碼public class UserLogProxy implements MethodInterceptor { ?? ?/** ?? ? * 生成 CGLIB 動態(tài)代理類方法 ?? ? * ?? ? * @param target ?? ? * @return ?? ? */ ?? ?public Object getLogProxy(Object target) { ?? ? ? ?// 增強(qiáng)器類,用來創(chuàng)建動態(tài)代理類 ?? ? ? ?Enhancer enhancer = new Enhancer(); ? ? ? ? ?// 設(shè)置代理類的父類字節(jié)碼對象 ?? ? ? ?enhancer.setSuperclass(target.getClass()); ? ? ? ? ?// 設(shè)置回調(diào) ?? ? ? ?enhancer.setCallback(this); ? ? ? ? ?// 創(chuàng)建動態(tài)代理對象并返回 ?? ? ? ?return enhancer.create(); ? ? ?} ? ? ?/** ?? ? * @param o ? ? ? ? 代理對象 ?? ? * @param method ? ? ?目標(biāo)對象中的方法的Method實例 ?? ? * @param objects ? ? 實際參數(shù) ?? ? * @param methodProxy ? 代理類對象中的方法的Method實例 ?? ? * @return ?? ? * @throws Throwable ?? ? */ ?? ?@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; ?? ?} }
測試程序:JavaMainTest
java復(fù)制代碼public class JavaMainTest { ?? ?public static void main(String[] args) { ? ? ? ? ?// 目標(biāo)對象 ?? ? ? ?UserServiceImpl userService = new UserServiceImpl(); ?? ? ? ?System.out.println(userService.getClass()); ? ? ? ? ?// 代理對象 ?? ? ? ?UserServiceImpl proxy = (UserServiceImpl) new UserLogProxy().getLogProxy(userService); ?? ? ? ?System.out.println(proxy.getClass()); ? ? ? ? ?List<String> list = proxy.findUserList(); ?? ? ? ?System.out.println("用戶信息:" + list); ? ? ? ? ?while (true) { ? ? ? ? ?} ?? ?} }
結(jié)果:
java復(fù)制代碼class com.study.spring.proxy.UserServiceImpl class com.study.spring.proxy.UserServiceImpl$$EnhancerByCGLIB$$cd9788d 前置輸出 用戶信息:[小A]
2.2 cglib代理流程
按照上述我們分析 $Proxy0
的方法,將 com.study.spring.proxy.UserServiceImpl$$EnhancerByCGLIB$$cd9788d
取出,得到如下:
java復(fù)制代碼public class UserServiceImpl$$EnhancerByCGLIB$$cd9788d extends UserServiceImpl implements Factory { ?? ?final List findUserList() { ?? ? ? ?// 是否設(shè)置了回調(diào) ?? ? ? ?MethodInterceptor methodInterceptor = this.CGLIB$CALLBACK_0; ?? ? ? ?if (methodInterceptor == null) { ?? ? ? ? ? ?UserServiceImpl$$EnhancerByCGLIB$$cd9788d.CGLIB$BIND_CALLBACKS(this); ?? ? ? ? ? ?methodInterceptor = this.CGLIB$CALLBACK_0; ?? ? ? ?} ?? ? ? ?// 設(shè)置回調(diào),需要調(diào)用 intercept 方法 ?? ? ? ?if (methodInterceptor != null) { ?? ? ? ? ? ?return (List) methodInterceptor.intercept(this, CGLIB$findUserList$0$Method, CGLIB$emptyArgs, CGLIB$findUserList$0$Proxy); ?? ? ? ?} ?? ? ? ?// 無回調(diào),調(diào)用父類的 findUserList 即可 ?? ? ? ?return super.findUserList(); ?? ?} ?? ?final List CGLIB$findUserList$0() { ?? ? ? ?return super.findUserList(); ?? ?} }
博主先把整個流程圖放到下面,然后結(jié)合流程圖來進(jìn)行講解:
在
JVM
編譯期間,我們的Enhancer
會根據(jù)目標(biāo)類的信息去動態(tài)的生成動態(tài)代理類
并設(shè)置回調(diào)
當(dāng)用戶在通過上述的動態(tài)代理類執(zhí)行
findUserList()
方法時,有兩個執(zhí)行選項若設(shè)置了回調(diào)接口,則直接調(diào)用
UserLogProxy
中的intercept
,然后通過FastClass
類調(diào)用動態(tài)代理類,執(zhí)行CGLIB$findUserList$0
方法,調(diào)用父類的findUserList()
方法若沒有設(shè)置回調(diào)接口,則直接調(diào)用父類的
findUserList()
方法
五、代理模式總結(jié)
1、三種代理模式實現(xiàn)方式的對比
jdk
代理和CGLIB
代理使用
CGLib
實現(xiàn)動態(tài)代理,CGLib
底層采用ASM
字節(jié)碼生成框架,使用字節(jié)碼技術(shù)生成代理類,在JDK1.6
之前比使用Java
反射效率要高。唯一需要注意的是,CGLib
不能對聲明為final
的類或者方法進(jìn)行代理,因為CGLib
原理是動態(tài)生成被代理類的子類。在
JDK1.6
、JDK1.7
、JDK1.8
逐步對JDK
動態(tài)代理優(yōu)化之后,在調(diào)用次數(shù)較少的情況下,JDK
代理效率高于CGLib
代理效率,只有當(dāng)進(jìn)行大量調(diào)用的時候,JDK1.6
和JDK1.7
比CGLib
代理效率低一點,但是到JDK1.8
的時候,JDK
代理效率高于CGLib
代理。所以如果有接口使用JDK
動態(tài)代理,如果沒有接口使用CGLIB
代理。動態(tài)代理和靜態(tài)代理
動態(tài)代理與靜態(tài)代理相比較,最大的好處是接口中聲明的所有方法都被轉(zhuǎn)移到調(diào)用處理器一個集中的方法中處理(InvocationHandler.invoke)。這樣,在接口方法數(shù)量比較多的時候,我們可以進(jìn)行靈活處理,而不需要像靜態(tài)代理那樣每一個方法進(jìn)行中轉(zhuǎn)。
如果接口增加一個方法,靜態(tài)代理模式除了所有實現(xiàn)類需要實現(xiàn)這個方法外,所有代理類也需要實現(xiàn)此方法。增加了代碼維護(hù)的復(fù)雜度。而動態(tài)代理不會出現(xiàn)該問題
2、代理模式優(yōu)缺點
優(yōu)點:
代理模式在客戶端與目標(biāo)對象之間起到一個中介作用和保護(hù)目標(biāo)對象的作用;
代理對象可以擴(kuò)展目標(biāo)對象的功能;
代理模式能將客戶端與目標(biāo)對象分離,在一定程度上降低了系統(tǒng)的耦合度;
缺點:
增加了系統(tǒng)的復(fù)雜度;
3、代理模式使用場景
功能增強(qiáng)
當(dāng)需要對一個對象的訪問提供一些額外操作時,可以使用代理模式
遠(yuǎn)程(Remote)代理
實際上,RPC 框架也可以看作一種代理模式,GoF 的《設(shè)計模式》一書中把它稱作遠(yuǎn)程代理。通過遠(yuǎn)程代理,將網(wǎng)絡(luò)通信、數(shù)據(jù)編解碼等細(xì)節(jié)隱藏起來??蛻舳嗽谑褂?RPC 服務(wù)的時候,就像使用本地函數(shù)一樣,無需了解跟服務(wù)器交互的細(xì)節(jié)。除此之外,RPC 服務(wù)的開發(fā)者也只需要開發(fā)業(yè)務(wù)邏輯,就像開發(fā)本地使用的函數(shù)一樣,不需要關(guān)注跟客戶端的交互細(xì)節(jié)。
防火墻(Firewall)代理
當(dāng)你將瀏覽器配置成使用代理功能時,防火墻就將你的瀏覽器的請求轉(zhuǎn)給互聯(lián)網(wǎng);當(dāng)互聯(lián)網(wǎng)返回響應(yīng)時,代理服務(wù)器再把它轉(zhuǎn)給你的瀏覽器。
保護(hù)(Protect or Access)代理
控制對一個對象的訪問,如果需要,可以給不同的用戶提供不同級別的使用權(quán)限。
六、結(jié)尾
終于寫完了這篇文章,動態(tài)代理在我看 AOP
源碼時,就感覺挺抽象的
我感覺最大的原因應(yīng)該在于:代理類動態(tài)生成,無法查看,導(dǎo)致對其模糊,從而陷入不理解
但通過這篇文章,我相信,99%
的人應(yīng)該都可以理解了動態(tài)代理模式的來龍去脈
當(dāng)然,好刀要用在刀刃上,在面試中,若面試官提及 設(shè)計模式
、動態(tài)代理
、Spring
、Dubbo
都可以引出動態(tài)代理,基本這篇文章無差別秒殺
如果你能看到這,那博主必須要給你一個大大的鼓勵,謝謝你的支持!
喜歡的可以點個關(guān)注,后續(xù)會更新 Spring
源碼系列文章
我是愛敲代碼的小黃,獨角獸企業(yè)的Java開發(fā)工程師,CSDN博客專家,Java領(lǐng)域新星創(chuàng)作者,喜歡后端架構(gòu)和中間件源碼。