Spring 的聲明式緩存注解(@Cacheable
, @CachePut
, @CacheEvict
等)是 AOP 技術在實際應用中最強大、最經典的范例之一,其原理與 @Transactional
非常相似。
核心思想:一個智能的“秘書”
你可以把 @Cacheable
的 AOP 實現想象成一個極其高效的“秘書”。
- 你 (調用方):老板。
- 目標方法 (
findProductById
): 一個需要花費大量時間和精力去調研才能回答的“問題”(比如去檔案館查資料)。 - AOP 代理 (Proxy): 你的秘書。
- 緩存 (Cache): 秘書的“備忘錄”或“筆記”。
當你向秘書提出一個問題時(調用方法):
- 秘書首先會翻看自己的備忘錄(查緩存)。
- 如果備忘錄里有答案(緩存命中),秘書會直接告訴你答案,然后回去繼續做其他事。他根本不會去檔案館進行那次耗時又費力的調研。你作為老板,很快就得到了答案,甚至不知道秘書是直接給的還是去查了資料。
- 如果備忘錄里沒有答案(緩存未命中),秘書會告訴你:“老板,請稍等,我需要去查一下。” 然后他會親自去檔案館進行調研(執行目標方法)。
- 當他拿到調研結果后,他會非常聰明地先把結果記在自己的備忘錄里(放入緩存),以備你下次再問。
- 最后,他才把這個調研結果告訴你。
@Cacheable
的 AOP 實現原理
@Cacheable
的背后是一個功能極其強大的環繞通知 (@Around
),由 Spring 的 CacheInterceptor
實現。
-
代理和攔截:和事務一樣,當 Spring 發現一個 Bean 的方法上有
@Cacheable
注解時,會為它創建一個 AOP 代理對象。所有對該方法的調用都會先被這個代理對象攔截。 -
CacheInterceptor
(環繞通知) 開始工作:
這個攔截器在調用真正的目標方法之前,會執行以下邏輯:a. 生成緩存鍵 (Cache Key):
* 攔截器會解析@Cacheable
注解。
* 它使用value
屬性 (如"products"
) 作為緩存的命名空間(或稱為緩存區域)。
* 它使用key
屬性 (如"#id"
) 中的 SpEL (Spring Expression Language) 表達式來根據方法參數動態生成一個唯一的鍵。例如,當調用findProductById(123L)
時,#id
會被替換為123L
,最終生成的鍵可能是"products::123"
。b. 查詢緩存:
* 攔截器拿著這個生成的鍵,去配置好的緩存管理器(CacheManager
,其背后可能是 Redis, Caffeine, EhCache 等)中進行查詢。 -
決策點:緩存是否命中?
這是環繞通知威力最大的體現,因為它可以控制目標方法是否執行。-
情況一:緩存命中 (Cache Hit)
- 攔截器在緩存中找到了對應的值。
- 它會立即將這個值返回給調用方。
- 最關鍵的一步:目標方法
findProductById
根本不會被執行! 這就是所謂的“方法短路 (Short-Circuiting)”。它避免了數據庫查詢或遠程調用。
-
情況二:緩存未命中 (Cache Miss)
- 攔截器在緩存中沒有找到任何東西。
- 它會繼續執行調用鏈,即調用
pjp.proceed()
來執行原始的目標方法findProductById
。 - 當目標方法成功執行并返回一個結果后,攔截器會捕獲這個返回值。
- 它會將這個返回值以之前生成的緩存鍵存入緩存中,以備下次使用。
- 最后,將這個返回值交還給調用方。
-
其他緩存注解的 AOP 原理
-
@CachePut
:- 這個注解同樣使用 AOP 攔截。
- 但它的邏輯是總會執行目標方法(比如更新數據庫的操作)。
- 在方法執行成功后,它會總是將方法的返回值更新到緩存中。
- 它用于確保在數據更新后,緩存中的數據也是最新的。它沒有“短路”行為。
-
@CacheEvict
:- 這個注解也使用 AOP 攔截。
- 它的邏輯通常是在目標方法成功執行后(默認),根據
key
去緩存中刪除一個或多個條目。 - 它用于在刪除或修改數據后,清除相關的舊緩存,確保數據一致性。
總結
注解 | AOP 實現方式 (簡化理解) | 是否執行目標方法? |
---|---|---|
@Cacheable | @Around :查緩存 -> (命中) 短路返回 / (未命中) 執行并存入緩存 | 不一定 |
@CachePut | @Around :執行方法 -> 將結果更新到緩存 | 總是執行 |
@CacheEvict | @AfterReturning (默認):執行方法 -> 從緩存中刪除條目 | 總是執行 |
所以,Spring 的聲明式緩存是 AOP 的實踐之一。它通過 AOP 將復雜的、與業務無關的緩存管理邏輯完全分離,讓開發者只需關注業務本身,極大地提高了代碼的可讀性和可維護性。