







這是一個(gè)困擾我司由來已久的難題,Dubbo 了解過吧,對外提供的服務(wù)可能有多個(gè)方法,一般我們?yōu)榱瞬唤o調(diào)用方埋坑,會在每個(gè)方法里把所有異常都 catch 住,只返回一個(gè) result,調(diào)用方會根據(jù)這個(gè) result 里的 success 判斷此次調(diào)用是否成功,舉個(gè)例子
public class ServiceResultTO<T> extends Serializable { private static final long serialVersionUID = xxx; private Boolean success; private String message; private T data; } public interface TestService { ServiceResultTOtest(); } public class TestServiceImpl implements TestService { @Override public ServiceResultTOtest() { try { // 此處寫服務(wù)里的執(zhí)行邏輯 return ServiceResultTO.buildSuccess(Boolean.TRUE); } catch(Exception e) { return ServiceResultTO.buildFailed(Boolean.FALSE, "執(zhí)行失敗"); } } }比如現(xiàn)在以上這樣的 dubbo 服務(wù)(TestService),它有一個(gè) test 方法,為了執(zhí)行正常邏輯時(shí)出現(xiàn)異常,我們在此方法執(zhí)行邏輯外包了一層「try... catch...」如果只有一個(gè) test 方法,這樣做當(dāng)然沒問題,但問題是在工程里我們一般要要提供幾十上百個(gè) service,每個(gè) service 有幾十個(gè)像 test 這樣的方法,如果每個(gè)方法都要在執(zhí)行的時(shí)候包一層 「try ...catch...」,雖然可行,但代碼會比較丑陋,可讀性也比較差,你能想想辦法改進(jìn)一下嗎?


既然是用切面解決的,我先解釋下什么是切面。我們知道,面向?qū)ο髮⒊绦虺橄蟪啥鄠€(gè)層次的對象,每個(gè)對象負(fù)責(zé)不同的模塊,這樣的話各個(gè)對象分工明確,各司其職,也不互相藕合,確實(shí)有力地促進(jìn)了工程開發(fā)與分工協(xié)作,但是新的問題來了,不同的模塊(對象)間有時(shí)會出現(xiàn)公共的行為,這種公共的行為很難通過繼承的方式來實(shí)現(xiàn),如果用工具類的話也不利于維護(hù),代碼也顯得異常繁瑣。切面(AOP)的引入就是為了解決這類問題而生的,它要達(dá)到的效果是保證開發(fā)者在不修改源代碼的前提下,為系統(tǒng)中不同的業(yè)務(wù)組件添加某些通用功能。

舉個(gè)例子來說說

比如上面這個(gè)例子,三個(gè) service 對象執(zhí)行過程中都存在安全,事務(wù),緩存,性能等相同行為,這些相同的行為顯然應(yīng)該在同一個(gè)地方管理,有人說我可以寫一個(gè)統(tǒng)一的工具類,在這些對象的方法前/后都嵌入此工具類,那問題來了,這些行為都屬于業(yè)務(wù)無關(guān)的,使用工具類嵌入的方式導(dǎo)致與業(yè)務(wù)代碼緊藕合,很不合工程規(guī)范,代碼可維護(hù)性極差!切面就是為了解決此類問題應(yīng)運(yùn)而生的,能做到相同功能的統(tǒng)一管理,對業(yè)務(wù)代碼無侵入

以性能為例,這些對象負(fù)責(zé)的模塊存在哪些相似的功能呢

比如說吧,每個(gè) service 都有不同的方法,我想統(tǒng)計(jì)每個(gè)方法的執(zhí)行時(shí)間,如果不用切面你需要在每個(gè)方法的首尾計(jì)算下時(shí)間,然后相減
![]()
如果我要統(tǒng)計(jì)每一個(gè) service 中每個(gè)方法的執(zhí)行時(shí)間可想而知不用切面的話就得在每個(gè)方法的首尾都加上類似上述的邏輯,顯然這樣的代碼可維護(hù)性是非常差的,這還只是統(tǒng)計(jì)時(shí)間,如果此方法又要加上事務(wù),風(fēng)控等,是不是也得在方法首尾加上事務(wù)開始,回滾等代碼,可想而知業(yè)務(wù)代碼與非業(yè)務(wù)代碼嚴(yán)重藕合,這樣的實(shí)現(xiàn)方式對工程是一種災(zāi)難,是不能接受的!

那如果用切面該怎么做呢

在說解決方案前,首先我們要看下與切面相關(guān)的幾個(gè)定義
JoinPoint: 程序在執(zhí)行流程中經(jīng)過的一個(gè)個(gè)時(shí)間點(diǎn),這個(gè)時(shí)間點(diǎn)可以是方法調(diào)用時(shí),或者是執(zhí)行方法中異常拋出時(shí),也可以是屬性被修改時(shí)等時(shí)機(jī),在這些時(shí)間點(diǎn)上你的切面代碼是可以(注意是可以但未必)被注入的
Pointcut: JoinPoints 只是切面代碼可以被織入的地方,但我并不想對所有的 JoinPoint 進(jìn)行織入,這就需要某些條件來篩選出那些需要被織入的 JoinPoint,Pointcut 就是通過一組規(guī)則(使用 AspectJ pointcut expression language 來描述) 來定位到匹配的 joinpoint
Advice: 代碼織入(也叫增強(qiáng)),Pointcut 通過其規(guī)則指定了哪些 joinpoint 可以被織入,而 Advice 則指定了這些 joinpoint 被織入(或者增強(qiáng))的具體時(shí)機(jī)與邏輯,是切面代碼真正被執(zhí)行的地方,主要有五個(gè)織入時(shí)機(jī)
畫外音:織入(weaving),將切面作用于委托類對象以創(chuàng)建 adviced object 的過程(即代理,下文會提)
- Before Advice: 在 JoinPoints 執(zhí)行前織入
- After Advice: 在 JoinPoints 執(zhí)行后織入(不管是否拋出異常都會織入)
- After returning advice: 在 JoinPoints 執(zhí)行正常退出后織入(拋出異常則不會被織入)
- After throwing advice: 方法執(zhí)行過程中拋出異常后織入
- Around Advice: 這是所有 Advice 中最強(qiáng)大的,它在 JoinPoints 前后都可織入切面代碼,也可以選擇是否執(zhí)行原有正常的邏輯,如果不執(zhí)行原有流程,它甚至可以用自己的返回值代替原有的返回值,甚至拋出異常。在這些 advice 里我們就可以寫入切面代碼了 綜上所述,切面(Aspect)我們可以認(rèn)為就是 pointcut 和 advice,pointcut 指定了哪些 joinpoint 可以被織入,而 advice 則指定了在這些 joinpoint 上的代碼織入時(shí)機(jī)與邏輯

列了一大堆概念真讓人生氣,請用你奶奶都能聽得懂的語言來解釋一下這些概念!


把技術(shù)解釋得讓非技術(shù)的人也聽懂才叫本事,這才說明你真的懂了。

這也難不倒我,比如在餐館里點(diǎn)菜,菜單有 10 個(gè)菜,這 10 個(gè)菜就是 JoinPoint,但我只點(diǎn)了帶有蘿卜名字的菜,那么帶有蘿卜名字這個(gè)條件就是針對 JoinPoint(10 個(gè)菜)的篩選條件,即 pointcut,最終只有胡蘿卜,白蘿卜這兩個(gè) JoinPoint 滿足條件,然后我們就可以在吃胡蘿卜前洗手(before advice),或吃胡蘿卜后買單(after advice),也可以統(tǒng)計(jì)吃胡蘿卜的時(shí)間(around advice),這些洗手,買單,統(tǒng)計(jì)時(shí)間的動作都是與吃蘿卜這個(gè)業(yè)務(wù)動作解藕的,都是統(tǒng)一寫在 advice 的邏輯里

能否用程序?qū)崿F(xiàn)一下,talk is cheap, show me your code!

好嘞,讓你看下我的實(shí)力
public interface TestService { // 吃蘿卜 void eatCarrot(); // 吃蘑菇 void eatMushroom(); // 吃白菜 void eatCabbage(); } @Component public class TestServiceImpl implements TestService { @Override public void eatCarrot() { System.out.println("吃蘿卜"); } @Override public void eatMushroom() { System.out.println("吃蘑菇"); } @Override public void eatCabbage() { System.out.println("吃白菜"); } }假設(shè)有以上 TestService, 實(shí)現(xiàn)了吃蘿卜,吃蘑菇,吃白菜三個(gè)方法,這三個(gè)方法都用切面織入,所以它們都是 joinpoints,但現(xiàn)在我只想對吃蘿卜這個(gè) joinpoints 前后織入 advice,該怎么辦呢,首先當(dāng)然要聲明 pointcut 表達(dá)式,這個(gè)表達(dá)式表明只想織入吃蘿卜這個(gè) joinpoint,指明了之后再讓 advice 應(yīng)用于此 pointcut 不就完了,比如我想在吃蘿卜前洗手,吃蘿卜后買單,可以寫出如下切面邏輯
@Aspect @Component public class TestAdvice { // 1. 定義 PointCut @Pointcut("execution(* com.example.demo.api.TestServiceImpl.eatCarrot())") private void eatCarrot(){} // 2. 定義應(yīng)用于 JoinPoint 中所有滿足 PointCut 條件的 advice, 這里我們使用 around advice,在其中織入增強(qiáng)邏輯 @Around("eatCarrot()") public void handlerRpcResult(ProceedingJoinPoint point) throws Throwable { System.out.println("吃蘿卜前洗手"); // 原來的 TestServiceImpl.eatCarrot 邏輯,可視情況決定是否執(zhí)行 point.proceed(); System.out.println("吃蘿后買單"); } }可以看到通過 AOP 我們巧妙地在方法執(zhí)行前后執(zhí)行插入相關(guān)的邏輯,對原有執(zhí)行邏輯無任何侵入!

小子果然有兩把刷子,我們 HR 眼光不錯(cuò),還有一個(gè)問題,開頭我司的那個(gè)難題你用切面又是如何解決的呢。

這就要說到 PointCut 的 AspectJ pointcut expression language 聲明式表達(dá)式,這個(gè)表達(dá)式支持的類型比較全面,可以用正則,注解等來指定滿足條件的 joinpoint , 比如類名后加 .*(..) 這樣的正則表達(dá)式就代表這個(gè)類里面的所有方法都會被織入,使用 @annotation 的方式也可以指定對標(biāo)有這類注解的方法織入代碼

恩,可以,繼續(xù)

首先我們先定義一個(gè)如下注解
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface GlobalErrorCatch { }然后將所有 service 中方法里的 「try... catch...」移除掉,在方法簽名上加上上述我們定義好的注解
public class TestServiceImpl implements TestService { @Override @GlobalErrorCatch public ServiceResultTOtest() { // 此處寫服務(wù)里的執(zhí)行邏輯 boolean result = xxx; return ServiceResultTO.buildSuccess(result); } }然后再指定注解形式的 pointcuts 及 around advice
@Aspect @Component public class TestAdvice { // 1. 定義所有帶有 GlobalErrorCatch 的注解的方法為 Pointcut @Pointcut("@annotation(com.example.demo.annotation.GlobalErrorCatch)") private void globalCatch(){} // 2. 將 around advice 作用于 globalCatch(){} 此 PointCut @Around("globalCatch()") public Object handlerGlobalResult(ProceedingJoinPoint point) throws Throwable { try { return point.proceed(); } catch (Exception e) { System.out.println("執(zhí)行錯(cuò)誤" + e); return ServiceResultTO.buildFailed("系統(tǒng)錯(cuò)誤"); } } }通過這樣的方式,所有標(biāo)記著 GlobalErrorCatch 注解的方法都會統(tǒng)一在 handlerGlobalResult 方法里執(zhí)行,我們就可以在這個(gè)方法里統(tǒng)一 catch 住異常,所有 service 方法中又長又臭的 「try...catch...」全部干掉,真香!























按照大佬提供的思路,我首先打印了 TestServiceImp 這個(gè) bean 所屬的類
@Component public class TestServiceImpl implements TestService { @Override public void eatCarrot() { System.out.println("吃蘿卜"); } } @Aspect @Component public class TestAdvice { // 1. 定義 PointCut @Pointcut("execution(* com.example.demo.api.TestServiceImpl.eatCarrot())") private void eatCarrot(){} // 2. 定義應(yīng)用于 PointCut 的 advice, 這里我們使用 around advice @Around("eatCarrot()") public void handlerRpcResult(ProceedingJoinPoint point) throws Throwable { // 省略相關(guān)邏輯 } } @SpringBootApplication @EnableAspectJAutoProxy public class DemoApplication { public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(DemoApplication.class, args); TestService testService = context.getBean(TestService.class); System.out.println("testService = " + testService.getClass()); } }
打印后我果然發(fā)現(xiàn)了端倪,這個(gè) bean 的 class 居然不是 TestServiceImpl!而是com.example.demo.impl.TestServiceImpl EnhancerBySpringCGLIB$$705c68c7!

果然有長進(jìn),繼續(xù)說,為啥會生成這樣一個(gè)類

我們注意到類名中有一個(gè) EnhancerBySpringCGLIB ,注意 CGLiB,這個(gè)類就是通過它生成的動態(tài)代理

打住,先不要說動態(tài)代理,先談?wù)勆妒谴戆?

代理在生活中隨處可見,比如說我要買房,我一般不會直接和賣家對接,一般會和中介打交道,中介就是代理,賣家就是目標(biāo)對象,我就是調(diào)用者,代理不僅實(shí)現(xiàn)了目標(biāo)對象的行為(幫目標(biāo)對象賣房),還可以添加上自己的動作(收保證金,簽合同等),
用 UML 圖來表示就是下面這樣
Client 是直接和 Proxy 打交道的,Proxy 是 Client 要真正調(diào)用的 RealSubject 的代理,它確實(shí)執(zhí)行了 RealSubject 的 request 方法,不過在這個(gè)執(zhí)行前后 Proxy 也加上了額外的 PreRequest(),afterRequest() 方法,注意 Proxy 和 RealSubject 都實(shí)現(xiàn)了 Subject 這個(gè)接口,這樣在 Client 看起來調(diào)用誰是沒有什么分別的(面向接口編程,對調(diào)用方無感,因?yàn)閷?shí)現(xiàn)的接口方法是一樣的),Proxy 通過其屬性持有真正要代理的目標(biāo)對象(RealSubject)以達(dá)到既能調(diào)用目標(biāo)對象的方法也能在方法前后注入其它邏輯的目的

聽得我要睡著了,根據(jù)這個(gè) UML 來寫下相應(yīng)的實(shí)現(xiàn)類吧

沒問題,不過在此之前我要先介紹一下代理的類型,代理主要分為兩種類型:靜態(tài)代理和動態(tài)代理,動態(tài)代理又有 JDK 代理和 CGLib 代理兩種,我先解釋下靜態(tài)和動態(tài)的含義

好小子,邏輯清晰,繼續(xù)吧

要理解靜態(tài)和動態(tài)這兩個(gè)含義,我們首先需要理解一下 Java 程序的運(yùn)行機(jī)制
首先 Java 源代碼經(jīng)過編譯生成字節(jié)碼,然后再由 JVM 經(jīng)過類加載,連接,初始化成 Java 類型,可以看到字節(jié)碼是關(guān)鍵,靜態(tài)和動態(tài)的區(qū)別就在于字節(jié)碼生成的時(shí)機(jī)靜態(tài)代理:由程序員創(chuàng)建代理類或特定工具自動生成源代碼再對其編譯。在編譯時(shí)已經(jīng)將接口,被代理類(委托類),代理類等確定下來,在程序運(yùn)行前代理類的.class文件就已經(jīng)存在了動態(tài)代理:在程序運(yùn)行后通過反射創(chuàng)建生成字節(jié)碼再由 JVM 加載而成

好,那你寫下靜態(tài)代理吧

嘿嘿按這張 UML 類庫依葫蘆畫瓢,傻瓜也會
public interface Subject { public void request(); } public class RealSubject implements Subject { @Override public void request() { // 賣房 System.out.println("賣房"); } } public class Proxy implements Subject { private RealSubject realSubject; public Proxy(RealSubject subject) { this.realSubject = subject; } @Override public void request() { // 執(zhí)行代理邏輯 System.out.println("賣房前"); // 執(zhí)行目標(biāo)對象方法 realSubject.request(); // 執(zhí)行代理邏輯 System.out.println("賣房后"); } public static void main(String[] args) { // 被代理對象 RealSubject subject = new RealSubject(); // 代理 Proxy proxy = new Proxy(subject); // 代理請求 proxy.request(); } }

喲喲喲,"傻瓜也會",看把你能的,那你說下靜態(tài)代理有啥劣勢

靜態(tài)代理主要有兩大劣勢
- 代理類只代理一個(gè)委托類(其實(shí)可以代理多個(gè),但不符合單一職責(zé)原則),也就意味著如果要代理多個(gè)委托類,就要寫多個(gè)代理(別忘了靜態(tài)代理在編譯前必須確定)
- 第一點(diǎn)還不是致命的,再考慮這樣一種場景:如果每個(gè)委托類的每個(gè)方法都要被織入同樣的邏輯,比如說我要計(jì)算前文提到的每個(gè)委托類每個(gè)方法的耗時(shí),就要在方法開始前,開始后分別織入計(jì)算時(shí)間的代碼,那就算用代理類,它的方法也有無數(shù)這種重復(fù)的計(jì)算時(shí)間的代碼

回答的不錯(cuò),那該怎么改進(jìn)

嘿嘿,這就要提到動態(tài)代理了,靜態(tài)代理的這些劣勢主要是是因?yàn)樵诰幾g前這些代理類是確定的,如果這些代理類是動態(tài)生成的呢,是不是可以省略一大堆代理的代碼。

給你 5 分鐘你先寫一下 JDK 的動態(tài)代理并解釋其原理

動態(tài)代理分為 JDK 提供的動態(tài)代理和 Spring AOP 用到的 CGLib 生成的代理,我們先看下 JDK 提供的動態(tài)代理該怎么寫

這是代碼
// 委托類 public class RealSubject implements Subject { @Override public void request() { // 賣房 System.out.println("賣房"); } } import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class ProxyFactory { private Object target;// 維護(hù)一個(gè)目標(biāo)對象 public ProxyFactory(Object target) { this.target = target; } // 為目標(biāo)對象生成代理對象 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("計(jì)算開始時(shí)間"); // 執(zhí)行目標(biāo)對象方法 method.invoke(target, args); System.out.println("計(jì)算結(jié)束時(shí)間"); return null; } }); } public static void main(String[] args) { RealSubject realSubject = new RealSubject(); System.out.println(realSubject.getClass()); Subject subject = (Subject) new ProxyFactory(realSubject).getProxyInstance(); System.out.println(subject.getClass()); subject.request(); } }``` 打印結(jié)果如下: ```shell 原始類:class com.example.demo.proxy.staticproxy.RealSubject 代理類:class com.sun.proxy.$Proxy0 計(jì)算開始時(shí)間 賣房 計(jì)算結(jié)束時(shí)間我們注意到代理類的 class 為 com.sun.proxy.$Proxy0,它是如何生成的呢,注意到 Proxy 是在 java.lang.reflect 反射包下的,注意看看 Proxy 的 newProxyInstance 簽名
public static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h);
- loader: 代理類的ClassLoader,最終讀取動態(tài)生成的字節(jié)碼,并轉(zhuǎn)成 java.lang.Class 類的一個(gè)實(shí)例(即類),通過此實(shí)例的 newInstance() 方法就可以創(chuàng)建出代理的對象
- interfaces: 委托類實(shí)現(xiàn)的接口,JDK 動態(tài)代理要實(shí)現(xiàn)所有的委托類的接口
- InvocationHandler: 委托對象所有接口方法調(diào)用都會轉(zhuǎn)發(fā)到 InvocationHandler.invoke(),在 invoke() 方法里我們可以加入任何需要增強(qiáng)的邏輯 主要是根據(jù)委托類的接口等通過反射生成的

這樣的實(shí)現(xiàn)有啥好處呢

由于動態(tài)代理是程序運(yùn)行后才生成的,哪個(gè)委托類需要被代理到,只要生成動態(tài)代理即可,避免了靜態(tài)代理那樣的硬編碼,另外所有委托類實(shí)現(xiàn)接口的方法都會在 Proxy 的 InvocationHandler.invoke() 中執(zhí)行,這樣如果要統(tǒng)計(jì)所有方法執(zhí)行時(shí)間這樣相同的邏輯,可以統(tǒng)一在 InvocationHandler 里寫, 也就避免了靜態(tài)代理那樣需要在所有的方法中插入同樣代碼的問題,代碼的可維護(hù)性極大的提高了。

說得這么厲害,那么 Spring AOP 的實(shí)現(xiàn)為啥卻不用它呢

JDK 動態(tài)代理雖好,但也有弱點(diǎn),我們注意到 newProxyInstance 的方法簽名
public static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h);
注意第二個(gè)參數(shù) Interfaces 是委托類的接口,是必傳的, JDK 動態(tài)代理是通過與委托類實(shí)現(xiàn)同樣的接口,然后在實(shí)現(xiàn)的接口方法里進(jìn)行增強(qiáng)來實(shí)現(xiàn)的,這就意味著如果要用 JDK 代理,委托類必須實(shí)現(xiàn)接口,這樣的實(shí)現(xiàn)方式看起來有點(diǎn)蠢,更好的方式是什么呢,直接繼承自委托類不就行了,這樣委托類的邏輯不需要做任何改動,CGlib 就是這么做的

回答得不錯(cuò),接下來談?wù)?CGLib 動態(tài)代理吧

好嘞,開頭我們提到的 AOP 就是用的 CGLib 的形式來生成的,JDK 動態(tài)代理使用 Proxy 來創(chuàng)建代理類,增強(qiáng)邏輯寫在 InvocationHandler.invoke() 里,CGlib 動態(tài)代理也提供了類似的 Enhance 類,增強(qiáng)邏輯寫在 MethodInterceptor.intercept() 中,也就是說所有委托類的非 final 方法都會被方法攔截器攔截,在說它的原理之前首先來看看它怎么用的
public class MyMethodInterceptor implements MethodInterceptor { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("目標(biāo)類增強(qiáng)前?。。?); //注意這里的方法調(diào)用,不是用反射哦!?。?/span> Object object = proxy.invokeSuper(obj, args); System.out.println("目標(biāo)類增強(qiáng)后?。?!"); return object; } } public class CGlibProxy { public static void main(String[] args) { //創(chuàng)建Enhancer對象,類似于JDK動態(tài)代理的Proxy類,下一步就是設(shè)置幾個(gè)參數(shù) Enhancer enhancer = new Enhancer(); //設(shè)置目標(biāo)類的字節(jié)碼文件 enhancer.setSuperclass(RealSubject.class); //設(shè)置回調(diào)函數(shù) enhancer.setCallback(new MyMethodInterceptor()); //這里的creat方法就是正式創(chuàng)建代理類 RealSubject proxyDog = (RealSubject) enhancer.create(); //調(diào)用代理類的eat方法 proxyDog.request(); } }打印如下
代理類:class com.example.demo.proxy.staticproxy.RealSubject$$EnhancerByCGLIB$$889898c5 目標(biāo)類增強(qiáng)前?。。? 賣房 目標(biāo)類增強(qiáng)后?。。?/pre>可以看到主要就是利用 Enhancer 這個(gè)類來設(shè)置委托類與方法攔截器,這樣委托類的所有非 final 方法就能被方法攔截器攔截,從而在攔截器里實(shí)現(xiàn)增強(qiáng)

底層實(shí)現(xiàn)原理是啥

之前也說了它是通過繼承自委托類,重寫委托類的非 final 方法(final 方法不能重載),并在方法里調(diào)用委托類的方法來實(shí)現(xiàn)代碼增強(qiáng)的,它的實(shí)現(xiàn)大概是這樣
public class RealSubject { @Override public void request() { // 賣房 System.out.println("賣房"); } } /** 生成的動態(tài)代理類(簡化版)**/ public class RealSubject$$EnhancerByCGLIB$$889898c5 extends RealSubject { @Override public void request() { System.out.println("增強(qiáng)前"); super.request(); System.out.println("增強(qiáng)后"); } }可以看到它并不要求委托類實(shí)現(xiàn)任何接口,而且 CGLIB 是高效的代碼生成包,底層依靠 ASM(開源的 java 字節(jié)碼編輯類庫)操作字節(jié)碼實(shí)現(xiàn)的,性能比 JDK 強(qiáng),所以 Spring AOP 最終使用了 CGlib 來生成動態(tài)代理

CGlib 動態(tài)代理使用上有啥限制嗎

第一點(diǎn)之前已經(jīng)已經(jīng)說了,只能代理委托類中任意的非 final 的方法,另外它是通過繼承自委托類來生成代理的,所以如果委托類是 final 的,就無法被代理了(final 類不能被繼承)

小伙子,這次確實(shí)可以看出你作了非常充分的準(zhǔn)備,不過你答的這些網(wǎng)上都能搜到答案,為了防止一些候選人背書本,我這里還有最后一個(gè)問題:JDK 動態(tài)代理的攔截對象是通過反射的機(jī)制來調(diào)用被攔截方法的,CGlib 呢,它通過什么機(jī)制來提升了方法的調(diào)用效率。


嘿嘿,我猜到了你不知道,我告訴你吧,由于反射的效率比較低,所以 CGlib 采用了FastClass 的機(jī)制來實(shí)現(xiàn)對被攔截方法的調(diào)用。FastClass 機(jī)制就是對一個(gè)類的方法建立索引,通過索引來直接調(diào)用相應(yīng)的方法,建議參考下https://www.cnblogs.com/cruze/p/3865180.html這個(gè)鏈接好好學(xué)學(xué)


還有一個(gè)問題,我們通過打印類名的方式知道了 cglib 生成了 RealSubject EnhancerByCGLIB$$889898c5 這樣的動態(tài)代理,那么有反編譯過它的 class 文件來了解 cglib 代理類的生成規(guī)則嗎


也在參考鏈接里,既然出來面試,對每個(gè)技術(shù)點(diǎn)都要深挖才行,像 Redis, MQ 這些中間件等平時(shí)只會用是不行的,對這些技術(shù)一定要做到原理級別的了解,鑒于你最后兩題沒答出來,我認(rèn)為你造火箭能力還有待提高,先回去等通知吧





后記
AOP 是 Spring 一個(gè)非常重要的特性,通過切面編程有效地實(shí)現(xiàn)了不同模塊相同行為的統(tǒng)一管理,也與業(yè)務(wù)邏輯實(shí)現(xiàn)了有效解藕,善用 AOP 有時(shí)候能起到出奇制勝的效果,舉一個(gè)例子,我們業(yè)務(wù)中有這樣的一個(gè)需求,需要在不同模塊中一些核心邏輯執(zhí)行前過一遍風(fēng)控,風(fēng)控通過了,這些核心邏輯才能執(zhí)行,怎么實(shí)現(xiàn)呢,你當(dāng)然可以統(tǒng)一封裝一個(gè)風(fēng)控工具類,然后在這些核心邏輯執(zhí)行前插入風(fēng)控工具類的代碼,但這樣的話核心邏輯與非核心邏輯(風(fēng)控,事務(wù)等)就藕合在一起了,更好的方式顯然應(yīng)該用 AOP,使用文中所述的注解 + AOP 的方式,將這些非核心邏輯解藕到切面中執(zhí)行,讓代碼的可維護(hù)性大大提高了。
篇幅所限,文中沒有分析 JDK 和 CGlib 的動態(tài)代理生成的實(shí)現(xiàn),不過建議大家有余力的話還是可以看看,尤其是文末的參考鏈接,生成動態(tài)代理主要用到了反射的特性,不過我們知道反射存在一定的性能問題,為了提升性能,底層用了一些比如緩存字節(jié)碼,F(xiàn)astClass 之類的技術(shù)來提升性能,通讀源碼之后的,對反射的理解也會大大加深。