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