Spring AOP總結
更美觀清晰的版本在:Github
前面的章節:
[Spring AOP 1] 從零開始的JDK動態代理
[Spring AOP 2] 從零開始的CGLIB動態代理
[Spring AOP 3] Spring選擇代理
[Spring AOP 4] Spring AOP 切點匹配
[Spring AOP 5] 高級切面與低級切面:@Aspect
vs Advisor
[Spring AOP 6] 靜態通知調用
[Spring AOP 7] 動態通知調用
我們以日常在Spring Boot中編寫AOP進行方法增強的代碼為例來整體梳理AOP背后到底發生了什么。我們需要:
- 配置類:開啟代理
- 切面類:這里準備了一個前置通知和一個環繞通知
- 目標類
- 啟動類
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true) // 將會使用CGLIB代理
@ComponentScan(basePackages = "spring.aop.summarization")
public class AopConfig {}
@Slf4j
@Aspect
@Component
public class MyAspect {// 前置增強foo方法@Before("execution(* spring.aop.summarization.Target.foo(..))")public void beforeAdvice(JoinPoint joinPoint) {log.info("Foo is ready to do something...");}// 環繞增強bar方法@Around("execution(* spring.aop.summarization.Target.bar(..))")public Object executionTime(ProceedingJoinPoint joinPoint) throws Throwable {log.info("Bar is ready to do something...");long start = System.currentTimeMillis();Object result = joinPoint.proceed();long end = System.currentTimeMillis();log.info("Bar execution time: {}", end - start);return result;}
}
@Component
@Slf4j
public class Target {public void foo() {fooDoSomething();}public void bar() {barDoSomething();}public void fooDoSomething() {try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}public void barDoSomething() {try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}
}
@SpringBootApplication
public class MyApplication {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(MyApplication.class, args);Target target = (Target) context.getBean("target"); // 獲得代理對象target.foo(); // 代理對象將調用增強了的方法target.bar();}
}
我們運行啟動類,可以看到預期的輸出結果:
foo
方法被前置增強bar
方法被環繞增強
2025-05-08T11:16:12.382+01:00 INFO 13260 --- [ main] spring.aop.summarization.MyAspect : Foo is ready to do something...
2025-05-08T11:16:13.384+01:00 INFO 13260 --- [ main] spring.aop.summarization.MyAspect : Bar is ready to do something...
2025-05-08T11:16:14.386+01:00 INFO 13260 --- [ main] spring.aop.summarization.MyAspect : Bar execution time: 1002
接下來,我們主要關注Spring AOP中發生的事情。
-
解析切面定義
Spring 啟動時掃描所有@Aspect
注解的類(如MyAspect
),并把帶有@Before
、@Around
、@After
等方法標記的元信息提取出來,交給一個ReflectiveAspectJAdvisorFactory
去生成“候選”Advisor
。 -
組裝低級 Advisor
ReflectiveAspectJAdvisorFactory
會將每個切面方法分裝為一個Pointcut
+Advice
:- 對于
@Before("…")
,生成一個AspectJExpressionPointcut
和一個AspectJMethodBeforeAdvice
,再封裝成一個DefaultPointcutAdvisor
; - 對于
@Around("…")
,生成一個AspectJExpressionPointcut
和一個AspectJAroundAdvice
,再封裝成另一個DefaultPointcutAdvisor
; - 所有這些
Advisor
都被當成候選切面緩存下來,等待后續匹配。 - 同時,Spring 也注冊了一個關鍵的
BeanPostProcessor
:AnnotationAwareAspectJAutoProxyCreator
。AnnotationAwareAspectJAutoProxyCreator
后續會根據切面類型自動生成代理對象
- 對于
-
容器刷新 &
BeanPostProcessor
注冊
當SpringApplication.run(...)
完成掃描和注冊后,容器會把AnnotationAwareAspectJAutoProxyCreator
等所有BeanPostProcessor
都注冊到生命周期中。 -
Bean 實例化與初始化(省略細節)
對每一個普通 Bean(比如:Target
),- Spring會用構造器創建實例并進行依賴注入;
- 如果Bean沒有循環依賴,那么
AnnotationAwareAspectJAutoProxyCreator
創建代理對象的時機在Bean初始化后 - 如果Bean被檢測到有循環依賴,那么
AnnotationAwareAspectJAutoProxyCreator
創建代理對象的時機在依賴注入前
- 如果Bean沒有循環依賴,那么
- 進行Bean的后處理;
- 進入AOP代理邏輯;
- Spring會用構造器創建實例并進行依賴注入;
-
自動代理 (
wrapIfNecessary
)
在Bean的后處理中:AnnotationAwareAspectJAutoProxyCreator#wrapIfNecessary
判斷- 這個類是不是基礎設施(切面本身、Advisor、Advice 等)如果是,跳過;否則執行
findEligibleAdvisors()
- 這個類是不是基礎設施(切面本身、Advisor、Advice 等)如果是,跳過;否則執行
- 調用
findEligibleAdvisors()
:它會拿之前緩存的所有候選Advisor
,對每一個執行靜態匹配,找到所有匹配當前 Bean 的切面; - 若結果不為空,就新建一個
ProxyFactory
,決定用 JDK 代理還是 CGLIB,給它設置目標對象和接口/類信息;proxyTargetClass = false
&& 目標實現接口:JDKproxyTargetClass = false
&& 目標未實現接口:CGLIBproxyTargetClass = true
:CGLIB
- 把上一步過濾出的
Advisor
全部加到ProxyFactory
; - 調用
proxyFactory.getProxy()
,生成代理對象,并替換掉容器中原來的 Bean 引用。
-
代理對象注入
以后別的 Bean 依賴Target
時,Spring 注入的都是上面生成的代理(JdkDynamicAopProxy
或ObjenesisCglibDynamicAopProxy
),而不是直接的new Target()
。 -
運行時方法調用 & 攔截鏈執行
當調用target.foo()
或target.bar()
時:- 代理對象攔截調用,進入
JdkDynamicAopProxy#invoke
或CglibAopProxy#intercept
;- 先執行
AdvisedSupport#getInterceptorsAndDynamicInterceptionAdvice()
- 將所有通知統一轉換為靜態通知
- 或者拿到動態通知,但我們這個例子中沒有動態通知
- 再先根據
Advisor
列表為該方法構建MethodInterceptor
鏈ExposeInvocationInterceptor
會創建一個最外圍的ADVISOR
切面- 同時會將調用鏈存入自己的
ThreadLocal
中
- 先執行
- 調用
ReflectiveMethodInvocation.proceed()
,沿鏈執行(遞歸):- Before Advice(
@Before
)── 執行beforeAdvice()
; - Around Advice(
@Around
)── 調用proceed()
前邏輯; - 目標方法(
Target#foo
/bar
)實際上被調用; - 開始一層層退出,Around Advice后邏輯被執行
- 退出Before Advice
- Before Advice(
- 鏈條執行完畢,最終結果返回給調用者。
- 代理對象攔截調用,進入