首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

Spring AOP 代理機制詳解:接口代理與類代理衝突導致 @RequiresPermissions 失效的排查與解決

     編輯:Java 綜合教學

Spring AOP 代理機制詳解:接口代理與類代理衝突導致 @RequiresPermissions 失效的排查與解決

問題現象

在同一個 Service 方法上同時標註 @Transactional(事務管理)與 @RequiresPermissions(Shiro 權限控制)時,發現權限註解不生效,方法可被無權限用戶直接調用。

根因分析:雙重代理導致的「代理鏈」斷裂

Spring 默認優先使用 JDK 動態代理(基於接口)。當目標類實現了接口時,Spring 會為其創建接口代理對象(如 ExampleServiceImpl$$)。

  1. 第一層代理(@Transactional):基於接口代理,生成 Proxy Object One(實現了目標接口)。
  2. 第二層代理(@RequiresPermissions):同樣嘗試基於接口代理,但檢測到目標已是代理對象(非原始類),為了攔截內部方法調用,Spring 強制升級為 CGLIB 類代理,生成 Proxy Object Two(繼承自 Proxy Object One)。
  3. 衝突點:@RequiresPermissions 的攔截器掛載在 Proxy Object Two 上,而外部調用持有的是 Proxy Object One 的引用。調用鏈繞過了第二層代理,導致權限檢查失效。

解決方案:強制統一代理模式為 CGLIB(類代理)

將兩個切面都基於同一個 類代理 構建,避免雙重代理嵌套。

1. 修改 Shiro 配置(或對應 AOP 配置)

<!-- 強制使用 CGLIB 代理 -->
<aop:config proxy-target-class="true">
    <aop:advisor advice-ref="permissionAdvice" pointcut="execution(* com.example.service..*(..))" />
</aop:config>

2. 修改 Spring 事務配置

<tx:annotation-driven proxy-target-class="true" transaction-manager="transactionManager" />

關鍵屬性:proxy-target-class="true" 強制 Spring 使用 CGLIB 為目標類創建子類代理,而非 JDK 接口代理。

驗證結果

配置修改後,重啟應用,@RequiresPermissions 重新生效,未授權用戶調用將拋出 UnauthorizedException。

深度補充:為什麼不建議混合代理模式?

  • 代理鏈複雜化:混合模式易產生多層代理嵌套,調試困難,堆棧追蹤冗長。
  • 性能開銷:CGLIB 通過字節碼繼承實現,創建代理耗時略長於 JDK,但調用性能相當;混合模式無額外優勢。
  • final 方法限制:CGLIB 無法代理 final 方法,若目標類含 final 方法需謹慎評估。

最佳實踐建議

  1. 統一代理策略:在項目啟動配置中全局設置 proxy-target-class="true",或通過 spring.aop.proxy-target-class=true(Spring Boot)統一為類代理。
  2. 面向接口編程:儘量讓 Service 實現接口,保持 JDK 代理可用性,但在需強制類代理的場景(如無接口、方法非 public)下顯式開啟。
  3. 切面優先級控制:使用 @Order 或 Ordered 顯式指定切面執行順序,避免隱式排序導致的不可預期行為。

總結

Spring AOP 的代理機制靈活強大,但混合使用接口代理與類代理時易產生「代理鏈斷裂」問題。通過 proxy-target-class="true 統一代理模式,可徹底解決 @Transactional 與 @RequiresPermissions 等多切面共存時的失效難題,構建穩健的切面編程架構。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved