漲知識(shí)!Spring AOP還能這么玩,看看你的項(xiàng)目能否用上
環(huán)境:Spring5.3.23
本篇文章將介紹兩個(gè)主題:
- 控制流切入點(diǎn)(動(dòng)態(tài)切入點(diǎn))
- 引介通知
1. 簡(jiǎn)介
Spring AOP是Spring框架的一個(gè)重要組成部分,它允許開發(fā)者定義跨多個(gè)模塊的橫切關(guān)注點(diǎn),例如日志記錄、事務(wù)管理、安全等??刂屏髑腥牒鸵橥ㄖ荢pring AOP中的兩個(gè)關(guān)鍵特性,它們能夠增強(qiáng)程序的可維護(hù)性和可讀性。本文將深入探討這兩個(gè)特性的工作原理和使用方法。
控制流切入
控制流切入允許我們根據(jù)方法調(diào)用的控制流來(lái)定義切入點(diǎn)??刂屏髑腥朦c(diǎn)與當(dāng)前調(diào)用堆棧匹配。例如,如果連接點(diǎn)被com.pack.service包中的方法或PersonService類調(diào)用,它可能會(huì)觸發(fā)??刂屏髑腥朦c(diǎn)是通過(guò)使用org.springframework.aop.support.ControlFlowPointcut類指定的。
引介通知
引介通知能夠聲明被建議的對(duì)象實(shí)現(xiàn)給定的接口,并代表這些對(duì)象提供該接口的實(shí)現(xiàn)。簡(jiǎn)單說(shuō):你有個(gè)PersonService類,引介通知能夠讓你不修改代碼的情況下去實(shí)現(xiàn)你給定的任意接口(CommonDAO)。
2. 實(shí)戰(zhàn)案例
2.1 控制流切入點(diǎn)
準(zhǔn)備基礎(chǔ)類
@Component
public class PersonDAO {
public void save(String name) {
System.out.println("PersonDAO save method invoke...") ;
}
}
@Component
public class PersonService {
@Resource
private PersonDAO dao ;
public void save(String name) {
System.out.println("PersonService save method inovke...") ;
this.dao.save(name) ;
}
}
定義切面類Advisor
低級(jí)切面Advisor,平時(shí)使用的@Aspect算是高級(jí)切面類,而這些高級(jí)切面類最終會(huì)被轉(zhuǎn)換為Advisor低級(jí)切面類。
@Component
public class PackControlFlowAdvisor extends DefaultPointcutAdvisor {
private static MethodInterceptor logInterceptor = invocation -> {
System.out.println("before log...") ;
Object ret = invocation.proceed() ;
System.out.println("after log...") ;
return ret ;
} ;
// 要進(jìn)行匹配的類
private static Class<?> clazz = PersonService.class ;
// 要進(jìn)行匹配的方法(可以為null,這樣指定類中的所有方法都會(huì)被匹配攔截)
private static String methodName = "save" ;
private static ControlFlowPointcut pointcut = new ControlFlowPointcut(clazz, methodName) ;
public PackControlFlowAdvisor() {
super(pointcut, logInterceptor) ;
}
}
測(cè)試
PersonService ps = context.getBean(PersonService.class) ;
ps.save("王五") ;
控制臺(tái)輸出
PersonService save method inovke...
before log...
PersonDAO save method invoke...
after log...
PersonDAO中的save方法被攔截了。什么意思?怎么PersonDAO就被攔截了,先來(lái)看上面切點(diǎn)的定義ControlFlowPointcut
public class ControlFlowPointcut implements Pointcut, ClassFilter, MethodMatcher {
public boolean matches(Class<?> clazz) {
return true;
}
public boolean matches(Method method, Class<?> targetClass) {
return true;
}
public boolean isRuntime() {
return true;
}
public boolean matches(Method method, Class<?> targetClass, Object... args) {
// 取得當(dāng)前線程的整個(gè)執(zhí)行棧(方法的調(diào)用)
for (StackTraceElement element : new Throwable().getStackTrace()) {
if (element.getClassName().equals(this.clazz.getName()) &&
(this.methodName == null || element.getMethodName().equals(this.methodName))) {
return true;
}
}
return false;
}
}
通過(guò)在這個(gè)切點(diǎn)類能知道:
- 當(dāng)前容器中的所有類都會(huì)被代理;因?yàn)檫@里的類匹配直接返回true,2個(gè)參數(shù)的matches直接返回true,最后isRuntime返回true,最終執(zhí)行3個(gè)參數(shù)的matches方法。
- 每個(gè)類中方法的調(diào)用都會(huì)獲取當(dāng)前執(zhí)行的棧,都會(huì)進(jìn)行判斷類及方法是否被匹配。
結(jié)合上面的測(cè)試輸出結(jié)果,PersonDAO#save方法被攔截了,因?yàn)樗掀ヅ錀l件,在PersonService#save方法中調(diào)用了PersonDAO#save方法,那PersonDAO#save方法執(zhí)行棧中就包含了PersonService#save正好匹配了我們定義的切點(diǎn)。
簡(jiǎn)單說(shuō):某個(gè)類中的某個(gè)方法調(diào)用時(shí)會(huì)判斷當(dāng)前整個(gè)執(zhí)行棧中是否有設(shè)定好的類及方法,如果有則攔截當(dāng)前的方法(執(zhí)行通知)。
注意:控制流切入點(diǎn)比正常切入點(diǎn)慢10-15倍,但在某些情況下它們是有用的。所以大家還是慎重使用吧,畢竟所有的類都被代理了(當(dāng)然這里我們可以自定義matches來(lái)控制)。
2.2 引介通知
引介通知相對(duì)比較簡(jiǎn)單直接可以在@Aspect切面類中定義
注備基礎(chǔ)類
// 這個(gè)接口是我們準(zhǔn)備讓其它類實(shí)現(xiàn)的
public interface CommonManager {
void calc(int a, int b) ;
}
// 默認(rèn)實(shí)現(xiàn)
public class DefaultCommonManager implements CommonManager {
@Override
public void calc(int a, int b) {
System.out.printf("計(jì)算a + b = %d%n", (a + b)) ;
}
}
// 該類是我們將要通過(guò)引介增強(qiáng)讓其實(shí)現(xiàn)CommonManager類
@Component("us")
public class UserService {
public void save() {
System.out.println("UserService save...") ;
}
}
切面類
@Aspect
public static class CommonAspect {
/**
* 這樣聲明后,匹配的類就會(huì)自動(dòng)的實(shí)現(xiàn)這里指定的CommonManager接口,默認(rèn)的實(shí)現(xiàn)類是使用DefaultCommonManager
* value:該值決定了哪些類會(huì)被增強(qiáng)(實(shí)現(xiàn)指定的CommonManager接口)
*/
@DeclareParents(value = "com.pack.main.aop_introductionadviser.IntructionDeclareMain2.*+", defaultImpl = DefaultCommonManager.class)
public static CommonManager mixin;
}
注意:在這個(gè)切面類中我們并沒(méi)有定義@Before,@Around等同志。
測(cè)試
CommonManager c = (CommonManager) context.getBean("us") ;
c.calc(10, 20) ;
控制臺(tái)輸出
計(jì)算a + b = 30
UserService能正確的轉(zhuǎn)換為CommonManager類,這說(shuō)明UserService生成的代理類實(shí)現(xiàn)了CommonManager接口類,同時(shí)在執(zhí)行方法調(diào)用的時(shí)候使用的是我們制定的默認(rèn)實(shí)現(xiàn)類DefaultCommonManager。
總結(jié):控制流切入點(diǎn)(ControlFlowPointcut)和引介通知(@DeclareParents)是Spring AOP的兩個(gè)重要概念??刂屏髑腥朦c(diǎn)用于在特定的控制流條件下切入代碼,而引介通知?jiǎng)t讓目標(biāo)類具有更加強(qiáng)大的能力。