Spring AOP 再回首

Keeper導(dǎo)讀:最近在做業(yè)務(wù)操作日志相關(guān)的項目,剛好用的是AOP相關(guān)內(nèi)容,就又整理了下之前的筆記,看某免費教學(xué)視頻記錄下的內(nèi)容,入門篇~
一、AOP 前奏
假設(shè)我們原本有個計算器的程序,現(xiàn)在有這樣的需求:
在程序執(zhí)行期間記錄日志 希望計算器只處理正數(shù)運算
啥也不說了,悶頭干~
public interface ArithmeticCalculator {
void add(int i,int j);
void sub(int i,int j);
}
public void add(int i, int j) {
if(i < 0 || j < 0){
throw new IllegalArgumentException("Positive numbers only");
}
System.out.println("The method add begins with " + i + "," + j);
int result = i + j;
System.out.println("result: "+ result);
System.out.println("The method add ends with " + i + "," + j);
}
public void sub(int i, int j) {
if(i < 0 || j < 0){
throw new IllegalArgumentException("Positive numbers only");
}
System.out.println("The method sub begins with " + i + "," + j);
int result = i - j;
System.out.println("result: "+ result);
System.out.println("The method sub ends with " + i + "," + j);
}
問題
這么一通干,存在的問題:
代碼混亂:越來越多的非業(yè)務(wù)需求(日志和驗證等)加入后,原有的業(yè)務(wù)方法急劇膨脹。每個方法在處理核心邏輯的同時還必須兼顧其他多個關(guān)注點 代碼分散:以日志需求為例,只是為了滿足這個單一需求,就不得不在多個模塊(方法)里多次重復(fù)相同的日志代碼。如果日志需求發(fā)生變化,必須修改所有模塊
解決
這里就可以用動態(tài)代理來解決。可以參考之前的文章: 《面試官問 Spring AOP 中兩種代理模式的區(qū)別,我懵逼了》
代理設(shè)計模式的原理:使用一個代理將對象包裝起來,然后用該代理對象取代原始對象。任何對原始對象的調(diào)用都要通過代理。代理對象決定是否以及何時將方法調(diào)用轉(zhuǎn)到原始對象上。

1、我們用 JDK 動態(tài)代理來實現(xiàn)日志功能
JDK 動態(tài)代理主要涉及到
java.lang.reflect包中的兩個類:Proxy 和 InvocationHandler。InvocationHandler 是一個接口,通過實現(xiàn)該接口定義橫切邏輯,并通過反射機制調(diào)用目標類的代碼,動態(tài)將橫切邏輯和業(yè)務(wù)邏輯編制在一起。
Proxy 利用 InvocationHandler 動態(tài)創(chuàng)建一個符合某一接口的實例,生成目標類的代理對象。
原理是使用反射機制。
public class LoggingHandler implements InvocationHandler {
/**
* 被代理的目標對象
*/
private Object proxyObj;
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("The method " + method.getName() + " begins with " + Arrays.toString(args));
Object result = method.invoke(proxyObj,args);
System.out.println("The method " + method.getName() + " ends with " + Arrays.toString(args));
return result;
}
public Object createProxy(Object proxyObj){
this.proxyObj = proxyObj;
//返回一個代理對象
return Proxy.newProxyInstance(proxyObj.getClass().getClassLoader(),
proxyObj.getClass().getInterfaces(),this);
}
}
2、再用 CGLib 動態(tài)代理實現(xiàn)校驗功能
CGLIB 全稱為 Code Generation Library,是一個強大的高性能,高質(zhì)量的代碼生成類庫,可以在運行期擴展 Java 類與實現(xiàn) Java 接口,CGLib 封裝了 asm,可以再運行期動態(tài)生成新 的 class。
和 JDK 動態(tài)代理相比較:JDK 創(chuàng)建代理有一個限制,就是只能為接口創(chuàng)建代理實例, 而對于沒有通過接口定義業(yè)務(wù)方法的類,則可以通過 CGLIB 創(chuàng)建動態(tài)代理。
需要導(dǎo)入 cglib-nodep-***.jar
public class ValidationHandler implements MethodInterceptor {
/**
* 被代理的目標對象
*/
private Object targetObject;
public Object createProxy(Object targetObject){
this.targetObject = targetObject;
Enhancer enhancer = new Enhancer();
//設(shè)置代理目標
enhancer.setSuperclass(targetObject.getClass());
//設(shè)置回調(diào)
enhancer.setCallback(this);
return enhancer.create();
}
/**
* 在代理實例上處理方法調(diào)用并返回結(jié)果
* @param o : 代理類
* @param method :被代理的方法
* @param objects :該方法的參數(shù)數(shù)組
* @param methodProxy
*/
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
Object result = null;
for (Object object : objects) {
if(Integer.parseInt(object.toString()) < 0){
throw new IllegalArgumentException("Positive numbers only");
}
}
//執(zhí)行目標對象的方法
result = methodProxy.invoke(targetObject,objects);
return result;
}
}
3、使用
public static void main(String[] args) {
ValidationHandler validationHandler = new ValidationHandler();
LoggingHandler loggingHandler = new LoggingHandler();
//cglib 要求目標對象是個單獨的對象
ArithmeticCalculatorImpl calculatorImpl = new ArithmeticCalculatorImpl();
validationHandler.createProxy(calculatorImpl);
//JDK 動態(tài)代理要求目標對象是接口
ArithmeticCalculator calculator = new ArithmeticCalculatorImpl();
ArithmeticCalculator loggingProxy = (ArithmeticCalculator) loggingHandler.createProxy(calculator);
loggingProxy.add(1,2);
ArithmeticCalculatorImpl validationProxy = (ArithmeticCalculatorImpl) validationHandler.createProxy(calculatorImpl);
validationProxy.sub(-1,2);
}
二、AOP
AOP(Aspect-Oriented Programming,面向切面編程):是一種新的方法論,是對傳統(tǒng) OOP(Object-Oriented Programming,面向?qū)ο缶幊? 的補充
AOP 的主要編程對象是切面(aspect),而切面模塊化橫切關(guān)注點
在應(yīng)用 AOP 編程時,仍然需要定義公共功能,但可以明確的定義這個功能在哪里,以什么方式應(yīng)用,并且不必修改受影響的類。這樣一來橫切關(guān)注點就被模塊化到特殊的對象(切面)里
AOP 的好處:
每個事物邏輯位于一個位置,代碼不分散,便于維護和升級
業(yè)務(wù)模塊更簡潔,只包含核心業(yè)務(wù)代

AOP 核心概念
切面(aspect):類是對物體特征的抽象,切面就是對橫切關(guān)注點的抽象
橫切關(guān)注點:對哪些方法進行攔截,攔截后怎么處理,這些關(guān)注點稱之為橫切關(guān)注點
連接點(joinpoint):程序執(zhí)行的某個特定位置:如類某個方法調(diào)用前、調(diào)用后、方法拋出異常后等。連接點由兩個信息確定:方法表示的程序執(zhí)行點;相對點表示的方位。例如 ArithmethicCalculator#add() 方法執(zhí)行前的連接點,執(zhí)行點為 ArithmethicCalculator#add();方位為該方法執(zhí)行前的位置。
被攔截到的點,因為 Spring 只支持方法類型的連接點,所以在 Spring 中連接點指的就是被攔截到的方法,實際上連接點還可以是字段或者構(gòu)造器
切入點(pointcut):每個類都擁有多個連接點,例如 ArithmethicCalculator 的所有方法實際上都是連接點,即連接點是程序類中客觀存在的事務(wù)。AOP 通過切點定位到特定的連接點。類比:連接點相當于數(shù)據(jù)庫中的記錄,切點相當于查詢條件。切點和連接點不是一對一的關(guān)系,一個切點匹配多個連接點,切點通過
org.springframework.aop.Pointcut接口進行描述,它使用類和方法作為連接點的查詢條件。通知(advice):所謂通知指的就是指攔截到連接點之后要執(zhí)行的代碼,通知分為前置、后置、 異常、最終、環(huán)繞通知五類
目標對象(Target):代理的目標對象
代理(Proxy):向目標對象應(yīng)用通知之后創(chuàng)建的對象
織入(weave):將切面應(yīng)用到目標對象并導(dǎo)致代理對象創(chuàng)建的過程
引入(introduction):在不修改代碼的前提下,引入可以在運行期為類動態(tài)地添加一些方法或字段。
"橫切"的技術(shù),剖解開封裝的對象內(nèi)部,并將那些影響了多個類的公共行為封裝到一個可重用模塊, 并將其命名為"Aspect",即切面。所謂"切面",簡單說就是那些與業(yè)務(wù)無關(guān),卻為業(yè)務(wù)模塊所共同調(diào)用的邏輯或責(zé)任封裝起來,便于減少系統(tǒng)的重復(fù)代碼,降低模塊之間的耦合度,并有利于未來的可操作性和可維護性。
使用"橫切"技術(shù),AOP 把軟件系統(tǒng)分為兩個部分:核心關(guān)注點和橫切關(guān)注點。業(yè)務(wù)處理的主要流程是核心關(guān)注點,與之關(guān)系不大的部分是橫切關(guān)注點。
橫切關(guān)注點的一個特點是,他們經(jīng)常發(fā)生在核心關(guān)注點的多處,而各處基本相似,比如權(quán)限認證、日志、事物。
AOP 的作用在于分離系統(tǒng)中的各種關(guān)注點,將核心關(guān)注點和橫切關(guān)注點分離開來。
AOP 主要應(yīng)用場景有:
Authentication 權(quán)限 Caching 緩存 Context passing 內(nèi)容傳遞 Error handling 錯誤處理 Lazy loading 懶加載 Debugging 調(diào)試 logging, tracing, profiling and monitoring 記錄跟蹤、優(yōu)化、校準 Performance optimization 性能優(yōu)化 Persistence 持久化 Resource pooling 資源池 Synchronization 同步 Transactions 事務(wù)
三、Spring AOP
AspectJ:Java 社區(qū)里最完整最流行的 AOP 框架 在 Spring2.0 以上版本中,可以使用基于 AspectJ 注解或基于 XML 配置的 AOP
在 Spring 中啟用 AspectJ 注解支持
要在 Spring 應(yīng)用中使用 AspectJ 注解,必須在 classpath 下包含 AspectJ 類庫:
aopalliance.jar、aspectj.weaver.jar 和 spring-aspects.jar
將 aop Schema 添加到 <beans> 根元素中
要在 Spring IOC 容器中啟用 AspectJ 注解支持,只要在 Bean 配置文件中定義一個空的 XML 元素 <aop:aspectj-autoproxy>
當 Spring IOC 容器偵測到 Bean 配置文件中的 <aop:aspectj-autoproxy> 元素時,會自動為與 AspectJ 切面匹配的 Bean 創(chuàng)建代理
3.1 用 AspectJ 注解聲明切面
要在 Spring 中聲明 AspectJ 切面,只需要在 IOC 容器中將切面聲明為 Bean 實例。當在 Spring IOC 容器中初始化 AspectJ 切面之后,Spring IOC 容器就會為那些與 AspectJ 切面相匹配的 Bean 創(chuàng)建代理
在 AspectJ 注解中,切面只是一個帶有 @Aspect 注解的 Java 類
通知是標注有某種注解的簡單的 Java 方法
AspectJ 支持 5 種類型的通知注解:
@Before:前置通知,在方法執(zhí)行之前執(zhí)行 @After:后置通知,在方法執(zhí)行之后執(zhí)行 @AfterRunning:返回通知,在方法返回結(jié)果之后執(zhí)行 @AfterThrowing:異常通知,在方法拋出異常之后 @Around:環(huán)繞通知,圍繞著方法執(zhí)行
1、前置通知
前置通知:在方法執(zhí)行之前執(zhí)行的通知
前置通知使用 @Before 注解,并將切入點表達式的值作為注解值
@Aspect:標識這個類是一個切面
@Before:標識這個方法是個前置通知
execution 代表切點表達式,表示執(zhí)行 ArithmeticCalculator 接口的 add()方法* 代表匹配任意修飾符及任意返回值 參數(shù)列表中的 .. 代表匹配任意數(shù)量的參數(shù)
@Aspect
@Component
public class LogAspect {
private Logger log = LoggerFactory.getLogger(this.getClass());
/**
* 前置通知,目標方法調(diào)用前被調(diào)用
*/
@Before("execution(* priv.starfish.aop.aspect.Calculator.add(..))")
public void beforeAdvice(){
log.info("The method add begins");
}
}
利用方法簽名編寫 AspectJ 切入點表達式
最典型的切入點表達式是根據(jù)方法的簽名來匹配各種方法:
execution *priv.starfish.aop.ArithmeticCalculator.*(..):匹配 ArithmeticCalculator 中聲明的所有方法,第一個 *代表任意修飾符及任意返回值,第二個 * 代表任意方法,.. 代表匹配任意數(shù)量的參數(shù)。若目標類與接口與該切面在同一個包中,可以省略包名
execution public * ArithmeticCalculator.*(..):匹配 ArithmeticCalculator 接口的所有公有方法execution public double ArithmeticCalculator.*(..):匹配 ArithmeticCalculator 中返回 double 類型數(shù)值的方法execution public double ArithmeticCalculator.*(double, ..):匹配第一個參數(shù)為 double 類型的方法, .. 匹配任意數(shù)量任意類型的參數(shù)execution public double ArithmeticCalculator.*(double, double):匹配參數(shù)類型為 double, double 的方法
合并切入點表達式
AspectJ 可以使用 且(&&)、或(||)、非(!)來組合切入點表達式 在 Schema風(fēng)格下,由于在 XML 中使用“&&”需要使用轉(zhuǎn)義字符“&&”來代替,所以很不方便,因此 Spring AOP 提供了and、or、not 來代替 &&、||、!。
@Pointcut("execution(* *.add(int,int)) || execution(* *.sub(int,int))")
public void loggingOperation(){};
讓通知訪問當前連接點的細節(jié)
可以在通知方法中聲明一個類型為 JoinPoint 的參數(shù),然后就能訪問連接細節(jié)了,比如方法名稱和參數(shù)值等
@Before("execution(* priv.starfish.aop.aspect.Calculator.add(..))")
public void beforeAdvice(JoinPoint joinPoint) {
log.info("The method " + joinPoint.getSignature().getName()
+ "() begins with " + Arrays.toString(joinPoint.getArgs()));
}
2、后置通知
后置通知是在連接點完成之后執(zhí)行的,即連接點返回結(jié)果或者拋出異常的時候,下面的后置通知記錄了方法的終止 一個切面可以包括一個或者多個通知
@Aspect
@Component
public class LogAspect {
private Logger log = LoggerFactory.getLogger(this.getClass());
/**
* 前置通知,目標方法調(diào)用前被調(diào)用
*/
@Before("execution(* priv.starfish.aop.aspect.Calculator.add(..))")
public void beforeAdvice(){
log.info("The method add begins");
}
/**
* 后置通知,目標方法執(zhí)行完執(zhí)行
*/
@After("execution( * *.*(..))")
public void afterAdvice(JoinPoint joinPoint){
log.info("The method " + joinPoint.getSignature().getName() + "() ends");
}
}
3、返回通知
無論連接點是正常返回還是拋出異常,后置通知都會執(zhí)行。如果只想在連接點返回的時候記錄日志,應(yīng)該使用返回通知代替后置通知
在返回通知中訪問連接點的返回值
在返回通知中,只要將 returning 屬性添加到 @AfterReturning 注解中,就可以訪問連接點的返回值。該屬性的值即為用來傳入返回值的參數(shù)名稱 必須在通知方法的簽名中添加一個同名參數(shù),在運行時,Spring AOP 會通過這個參數(shù)傳遞返回值 原始的切點表達式需要出現(xiàn)在 pointcut 屬性中
/**
* 后置返回通知
* 如果參數(shù)中的第一個參數(shù)為JoinPoint,則第二個參數(shù)為返回值的信息
* 如果參數(shù)中的第一個參數(shù)不為JoinPoint,則第一個參數(shù)為returning中對應(yīng)的參數(shù)
* returning 只有目標方法返回值與通知方法相應(yīng)參數(shù)類型時才能執(zhí)行后置返回通知,否則不執(zhí)行
*/
@AfterReturning(value = "loggingOperation()",returning = "res")
public void afterReturningAdvice(JoinPoint joinPoint, Object res){
System.out.println("后置返回通知 返回值:"+res);
}
4、后置異常通知
只在連接點拋出異常時才執(zhí)行異常通知 將 throwing 屬性添加到 @AfterThrowing 注解中,也可以訪問連接點拋出的異常。Throwable 是所有錯誤和異常類的超類,所以在異常通知方法可以捕獲到任何錯誤和異常 如果只對某種特殊的異常類型感興趣,可以將參數(shù)聲明為其他異常的參數(shù)類型。然后通知就只在拋出這個類型及其子類的異常時才被執(zhí)行
/**
* 后置異常通知
* 定義一個名字,該名字用于匹配通知實現(xiàn)方法的一個參數(shù)名,當目標方法拋出異常返回后,將把目標方法拋出的異常傳給通知方法;
* throwing 只有目標方法拋出的異常與通知方法相應(yīng)參數(shù)異常類型時才能執(zhí)行后置異常通知,否則不執(zhí)行,
*/
@AfterThrowing(value = "loggingOperation()",throwing = "exception")
public void afterThrowingAdvice(JoinPoint joinPoint,ArithmeticException exception){
log.error(joinPoint.getSignature().getName() + "has throw an exception" + exception);
}
5、環(huán)繞通知
環(huán)繞通知是所有通知類型中功能最為強大的,能夠全面地控制連接點。甚至可以控制是否執(zhí)行連接點 對于環(huán)繞通知來說,連接點的參數(shù)類型必須是 ProceedingJoinPoint。它是 JoinPoint 的子接口,允許控制何時執(zhí)行,是否執(zhí)行連接點 在環(huán)繞通知中需要明確調(diào)用 ProceedingJoinPoint 的 proceed()方法來執(zhí)行被代理的方法,如果忘記這樣做就會導(dǎo)致通知被執(zhí)行了,但目標方法沒有被執(zhí)行注意:環(huán)繞通知的方法需要返回目標方法執(zhí)行之后的結(jié)果,即調(diào)用 joinPoint.proceed();的返回值,否則會出現(xiàn)空指針異常
/**
* 環(huán)繞通知:
* 環(huán)繞通知非常強大,可以決定目標方法是否執(zhí)行,什么時候執(zhí)行,執(zhí)行時是否需要替換方法參數(shù),執(zhí)行完畢是否需要替換返回值。
* 環(huán)繞通知第一個參數(shù)必須是org.aspectj.lang.ProceedingJoinPoint類型
*/
@Around("loggingOperation()")
public Object aroundAdvice(ProceedingJoinPoint joinPoint){
System.out.println("- - - - - 環(huán)繞前置通知 - - - -");
try {
//調(diào)用執(zhí)行目標方法
Object obj = joinPoint.proceed();
System.out.println("- - - - - 環(huán)繞后置返回通知 - - - -");
return obj;
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("- - - - - 環(huán)繞異常通知 - - - -");
}finally {
System.out.println("- - - - - 環(huán)繞后置通知 - - - -");
}
return null;
}
指定切面的優(yōu)先級
在同一個連接點上應(yīng)用不止一個切面時,除非明確指定,否則它們的優(yōu)先級是不確定的 切面的優(yōu)先級可以通過實現(xiàn) Ordered 接口或利用 @Order 注解指定 實現(xiàn) Ordered 接口, getOrder()方法的返回值越小,優(yōu)先級越高若使用 @Order 注解,序號出現(xiàn)在注解中
@Aspect
@Order(0)
public class ValidationAspect {
}
@Aspect
@Order(1)
public class LogAspect {
重用切入點定義
在編寫 AspectJ 切面時,可以直接在通知注解中書寫切入點表達式。但同一個切點表達式可能會在多個通知中重復(fù)出現(xiàn) 在 AspectJ 切面中,可以通過 @Pointcut 注解將一個切入點聲明成簡單的方法。切入點的方法體通常是空的,因為將切入點定義與應(yīng)用程序邏輯混在一起是不合理的 切入點方法的訪問控制符同時也控制著這個切入點的可見性。如果切入點要在多個切面中共用,最好將它們集中在一個公共的類中。在這種情況下,它們必須被聲明為 public。在引入這個切入點時,必須將類名也包括在內(nèi)。如果類沒有與這個切面放在同一個包中,還必須包含包名。 其他通知可以通過方法名稱引入該切入點
/**
* 切入點
*/
@Pointcut("execution(public int priv.starfish.aop.aspect.CalculatorImpl.*(int,int))")
public void executePackage(){};
/**
* 這里直接寫成 value= 調(diào)用了切入點 excution 表達式
*/
@AfterReturning(value = "executePackage()",returning = "res")
public void afterReturningAdvice(JoinPoint joinPoint, Object res){
System.out.println("- - - - - 后置返回通知- - - - -");
System.out.println("后置返回通知 返回值:"+res);
}
引入通知
引入通知是一種特殊的通知類型。它通過為接口提供實現(xiàn)類,允許對象動態(tài)地實現(xiàn)接口,就像對象已經(jīng)在運行時擴展了實現(xiàn)類一樣
引入通知可以使用兩個實現(xiàn)類 MaxCalculatorImpl 和 MinCalculatorImpl,讓 ArithmeticCalculatorImpl 動態(tài)地實現(xiàn) MaxCalculator和 MinCalculator接口。而這與從 MaxCalculatorImpl 和 MinCalculatorImpl 中實現(xiàn)多繼承的效果相同。但卻不需要修改 ArithmeticCalculatorImpl 的源代碼
引入通知也必須在切面中聲明
在切面中,通過為任意字段添加**@DeclareParents**注解來引入聲明
注解類型的 value 屬性表示哪些類是當前引入通知的目標。value 屬性值也可以是一個 AspectJ 類型的表達式,可以將一個接口引入到多個類中。defaultImpl屬性中指定這個接口使用的實現(xiàn)類
代碼在 starfish-learn-spring 上
3.2 用基于 XML 的配置聲明切面
除了使用 AspectJ 注解聲明切面,Spring 也支持在 Bean 配置文件中聲明切面。這種聲明是通過 aop schema 中的 XML 元素完成的
正常情況下,基于注解的聲明要優(yōu)先于基于 XML 的聲明。通過 AspectJ注解,切面可以與 AspectJ 兼容,而基于 XML 的配置則是 Spring 專有的。由于 AspectJ 得到越來越多的 AOP 框架支持,所以以注解風(fēng)格編寫的切面將會有更多重用的機會
當使用 XML 聲明切面時,需要在 <beans> 根元素中導(dǎo)入 aop Schema
在 Bean 配置文件中,所有的 Spring AOP 配置都必須定義在 <aop:config> 元素內(nèi)部。對于每個切面而言,都要創(chuàng)建一個 <aop:aspect>
元素來為具體的切面實現(xiàn)引用后端 Bean 實例
切面 Bean 必須有一個標識符,供 <aop:aspect> 元素引用
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.2.xsd">
<bean id="calculator" class="priv.starfish.aop.xml.CalculatorImpl" />
<bean id="timeHandler" class="priv.starfish.aop.xml.TimeHandler" />
<aop:config>
<aop:aspect id="time" ref="timeHandler">
<aop:pointcut id="addTime" expression="execution(* priv.starfish.aop.xml.*.*(..))"/>
<aop:before method="printTime" pointcut-ref="addTime" />
<aop:after method="printTime" pointcut-ref="addTime" />
</aop:aspect>
</aop:config>
</beans>
public class TimeHandler {
public void printTime() {
System.out.println("CurrentTime = " + System.currentTimeMillis());
}
}
public static void main(String[] args) {
ApplicationContext ctx =
new ClassPathXmlApplicationContext("applicationContext.xml");
Calculator calculator = (Calculator)ctx.getBean("calculator");
calculator.add(2,3);
}
3.2 AOP 兩種代理方式
Spring 中 AOP 代理由 Spring 的 IOC 容器負責(zé)生成、管理,其依賴關(guān)系也由 IOC 容器負責(zé)管理。因此,AOP 代理可以直接使用容器中
的其它 bean 實例作為目標,這種關(guān)系可由 IOC 容器的依賴注入提供。Spring創(chuàng)建代理的規(guī)則為:
默認使用 Java 動態(tài)代理來創(chuàng)建 AOP 代理,這樣就可以為任何接口實例創(chuàng)建代理了
當需要代理的類不是代理接口的時候,Spring會切換為使用CGLIB代理,也可強制使用 CGLIB
Spring 提供了兩種方式來生成代理對象:JDKProxy 和 Cglib,具體使用哪種方式生成由 AopProxyFactory 根據(jù) AdvisedSupport 對象
的配置來決定。默認的策略是如果目標類是接口, 則使用 JDK 動態(tài)代理技術(shù),否則使用 Cglib 來生成代理。
注:JDK 動態(tài)代理要比 cglib 代理執(zhí)行速度快,但性能不如 cglib 好。所以在選擇用哪種代理還是要看具體情況,一般單例模式用 cglib 比較好。


爛了大街的 Spring 循環(huán)依賴問題,你以為自己就真會了嗎
內(nèi)容編輯 | Keeper 文章來源 | 尚硅谷視頻學(xué)習(xí)筆記
