當(dāng)心!SpringBoot在這幾種情況下將導(dǎo)致代理失效
環(huán)境:SpringBoot2.7.18
1. 簡介
Spring AOP(面向切面編程)是Spring框架的核心特性之一,它以一種非侵入式的方式增強(qiáng)了應(yīng)用程序的模塊性和可維護(hù)性。通過AOP,開發(fā)者能夠?qū)M切關(guān)注點(diǎn)(如日志記錄、事務(wù)管理、安全控制等)從業(yè)務(wù)邏輯中分離出來,形成獨(dú)立的切面,從而實(shí)現(xiàn)了關(guān)注點(diǎn)的模塊化。這種分離不僅簡化了代碼結(jié)構(gòu),還提高了代碼的重用性和靈活性。Spring AOP利用代理機(jī)制在運(yùn)行時(shí)動(dòng)態(tài)地將切面織入到目標(biāo)對象中,無需修改原有代碼,極大降低了系統(tǒng)間的耦合度。
實(shí)現(xiàn)代理的核心元素
- 切入點(diǎn)
Pointcut定義了哪些方法會(huì)被增強(qiáng),而切點(diǎn)通常通過表達(dá)式來定義,這些表達(dá)式可以基于方法名、參數(shù)類型、注解等多種條件。
- 通知類
通知?jiǎng)t是你需要增強(qiáng)的邏輯,這其中包括了前置通知(Before Advice)、后置通知(After Advice)、環(huán)繞通知(Around Advice)、異常通知(Throws Advice)和引介通知(Introduction Advice)。
- 處理器
有了上面2個(gè)關(guān)鍵元素后,那如何才能創(chuàng)建代理呢?這時(shí)候的BeanPostProcessor就是最為關(guān)鍵的類了,它會(huì)根據(jù)切入點(diǎn)來判斷你當(dāng)前的bean是否符合條件,對于符合條件的則進(jìn)行代理的創(chuàng)建最終返回給Spring容器。Spring容器中保存的是代理對象。而在Spring中我們最常見的幾種注冊處理器的方式是:通過下面3個(gè)注解
@Configuration
// 開啟事務(wù)(針對的事務(wù)注解@Transactional)
@EnableTransactionManagement
// 開啟AOP代理(只要具備上面的1,2條件即可)
@EnableAspectJAutoProxy
// 開啟異步支持(針對的是@Async注解)
@EnableAsync
public class AppConfig {}
具備了上面3個(gè)核心元素后,是否就一定能為bean對象創(chuàng)建代理呢?這將是接下來要介紹的內(nèi)容。
2. 不創(chuàng)建代理情況
2.1 環(huán)境準(zhǔn)備
先準(zhǔn)備基礎(chǔ)環(huán)境進(jìn)行接下來的測試使用
@Service
public class Service {
public void save() {
System.out.println("Service save...") ;
}
}
將圍繞該Service創(chuàng)建代理
@Component
@Aspect
public class LogAspect {
@Pointcut("execution(* com.pack..*.*(..))")
private void log() {
}
@Before("log()")
public void recordLog() {
System.out.println("before log...") ;
}
}
該切面定義了一個(gè)前置通知,切入點(diǎn)匹配com.pack包及其子包下的所有方法。
2.2 正常創(chuàng)建代理
到此,以上定義沒有任何特殊的程序能正常的創(chuàng)建代理,如下示例:
ConfigurableApplicationContext context = SpringApplication.run(App.class, args) ;
Service service = context.getBean(Service.class);
System.out.println(service.getClass()) ;
service.save();
輸出結(jié)果
class com.pack.Service$$SpringCGLIB$$0
before log...
Service save ...
正常通過cglib創(chuàng)建代理對象。
2.3 不創(chuàng)建代理
- Service實(shí)現(xiàn)Advice接口
public class Service implements Advice {}
再次運(yùn)行后,輸出結(jié)果
class com.pack.Service
Service save...
沒有創(chuàng)建代理,沒有執(zhí)行通知方法。
- Service實(shí)現(xiàn)Pointcut接口
public class Service implements Pointcut {
// 該接口需要實(shí)現(xiàn)下面2個(gè)方法
// 這里無所謂,默認(rèn)實(shí)現(xiàn)即可
public ClassFilter getClassFilter() {
return null;
}
public MethodMatcher getMethodMatcher() {
return null;
}
}
輸出結(jié)果:
class com.pack.Service
Service save...
同樣,沒有創(chuàng)建代理:
- Service實(shí)現(xiàn)AopInfrastructureBean接口
public class Service implements AopInfrastructureBean {}
該接口沒有任何方法標(biāo)記接口基礎(chǔ)設(shè)施類,輸出結(jié)果
class com.pack.Service
Service save...
沒有創(chuàng)建代理
- Service實(shí)現(xiàn)Advisor接口
Spring創(chuàng)建代理對象,底層實(shí)現(xiàn)即使你通過注解@Aspect方式聲明的切面都會(huì)將其轉(zhuǎn)換為Advisor這種低級切面。
Advisor接口只有一個(gè)抽象方法。
public class Service implements Advisor {
// 空實(shí)現(xiàn)即可
public Advice getAdvice() {
return null ;
}
}
輸出結(jié)果與上面一樣,同樣不會(huì)創(chuàng)建代理。
- 特殊的beanName
給Service一個(gè)特殊的beanName。
@Component("com.pack.Service.ORIGINAL")
public class Service {}
這個(gè)beanName以當(dāng)前的完整包名+類名+.ORIGINAL命名,輸出結(jié)果:
class com.pack.Service
Service save...
沒有創(chuàng)建代理,修改beanName:
@Component("xxxooo.ORIGINAL")
當(dāng)修改成上面的名稱后,再次運(yùn)行:
class com.pack.Service$$SpringCGLIB$$0
before log...
Service save ...
被代理了,這說明beanName只有是"完整包名+類名+.ORIGINAL"才不會(huì)創(chuàng)建代理對象。
- 特殊的Advisor
該情況非常特殊也比較復(fù)雜,直接上代碼:
@Component
public class LogAdvisor extends AspectJPointcutAdvisor {
public LogAdvisor(AbstractAspectJAdvice advice) {
super(advice);
}
@Override
public String getAspectName() {
return "service" ;
}
}
只要上面getAspectName方法返回值與對應(yīng)Service的beanName一致也將不會(huì)創(chuàng)建代理。