摘要:起因考慮如下一個例子定義在這個例子中我們定義了一個注解這個是一個方法注解我們的期望是當有此注解的方法被調用時需要執行指定的切面邏輯即執行方法在類中方法被所注解因此調用方法時應該會觸發方法的調用不過有一點我
起因
考慮如下一個例子:
@Target(value = {ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface MyMonitor { }
@Component @Aspect public class MyAopAdviseDefine { private Logger logger = LoggerFactory.getLogger(getClass()); @Pointcut("@annotation(com.xys.demo4.MyMonitor)") public void pointcut() { } // 定義 advise @Before("pointcut()") public void logMethodInvokeParam(JoinPoint joinPoint) { logger.info("---Before method {} invoke, param: {}---", joinPoint.getSignature().toShortString(), joinPoint.getArgs()); } }
@Service public class SomeService { private Logger logger = LoggerFactory.getLogger(getClass()); public void hello(String someParam) { logger.info("---SomeService: hello invoked, param: {}---", someParam); test(); } @MyMonitor public void test() { logger.info("---SomeService: test invoked---"); } }
@EnableAspectJAutoProxy(proxyTargetClass = true) @SpringBootAppliMyion public class MyAopDemo { @Autowired SomeService someService; public static void main(String[] args) { SpringAppliMyion.run(MyAopDemo.class, args); } @PostConstruct public void aopTest() { someService.hello("abc"); } }
在這個例子中, 我們定義了一個注解 MyMonitor, 這個是一個方法注解, 我們的期望是當有此注解的方法被調用時, 需要執行指定的切面邏輯, 即執行 MyAopAdviseDefine.logMethodInvokeParam 方法.
在 SomeService 類中, 方法 test() 被 MyMonitor 所注解, 因此調用 test() 方法時, 應該會觸發 logMethodInvokeParam 方法的調用. 不過有一點我們需要注意到, 我們在 MyAopDemo 測試例子中, 并沒有直接調用 SomeService.test() 方法, 而是調用了 SomeService.hello() 方法, 在 hello 方法中, 調用了同一個類內部的 SomeService.test() 方法. 按理說, test() 方法被調用時, 會觸發 AOP 邏輯, 但是在這個例子中, 我們并沒有如愿地看到 MyAopAdviseDefine.logMethodInvokeParam 方法的調用, 這是為什么呢?
這是由于 Spring AOP (包括動態代理和 CGLIB 的 AOP) 的限制導致的. Spring AOP 并不是擴展了一個類(目標對象), 而是使用了一個代理對象來包裝目標對象, 并攔截目標對象的方法調用. 這樣的實現帶來的影響是: 在目標對象中調用自己類內部實現的方法時, 這些調用并不會轉發到代理對象中, 甚至代理對象都不知道有此調用的存在.
即考慮到上面的代碼中, 我們在 MyAopDemo.aopTest() 中, 調用了 someService.hello("abc"), 這里的 someService bean 其實是 Spring AOP 所自動實例化的一個代理對象, 當調用 hello() 方法時, 先進入到此代理對象的同名方法中, 然后在代理對象中執行 AOP 邏輯(因為 hello 方法并沒有注入 AOP 橫切邏輯, 因此調用它不會有額外的事情發生), 當代理對象中執行完畢橫切邏輯后, 才將調用請求轉發到目標對象的 hello() 方法上. 因此當代碼執行到 hello() 方法內部時, 此時的 this 其實就不是代理對象了, 而是目標對象, 因此再調用 SomeService.test() 自然就沒有 AOP 效果了.
簡單來說, 在 MyAopDemo 中所看到的 someService 這個 bean 和在 SomeService.hello() 方法內部上下文中的 this 其實代表的不是同一個對象(可以通過分別打印兩者的 hashCode 以驗證), 前者是 Spring AOP 所生成的代理對象, 而后者才是真正的目標對象(SomeService 實例).
解決弄懂了上面的分析, 那么解決這個問題就十分簡單了. 既然 test() 方法調用沒有觸發 AOP 邏輯的原因是因為我們以目標對象的身份(target object) 來調用的, 那么解決的關鍵自然就是以代理對象(proxied object)的身份來調用 test() 方法.
因此針對于上面的例子, 我們進行如下修改即可:
@Service public class SomeService { private Logger logger = LoggerFactory.getLogger(getClass()); @Autowired private SomeService self; public void hello(String someParam) { logger.info("---SomeService: hello invoked, param: {}---", someParam); self.test(); } @CatMonitor public void test() { logger.info("---SomeService: test invoked---"); } }
上面展示的代碼中, 我們使用了一種很 subtle 的方式, 即將 SomeService bean 注入到 self 字段中(這里再次強調的是, SomeService bean 實際上是一個代理對象, 它和 this 引用所指向的對象并不是同一個對象), 因此我們在 hello 方法調用中, 使用 self.test() 的方式來調用 test() 方法, 這樣就會觸發 AOP 邏輯了.
Spring AOP 導致的 @Transactional 不生效的問題這個問題同樣地會影響到 @Transactional 注解的使用, 因為 @Transactional 注解本質上也是由 AOP 所實現的.
例如我在 stackoverflow 上看到的一個類似的問題: Spring @Transaction method call by the method within the same class, does not work?
這里也記錄下來以作參考.
那個哥們遇到的問題如下:
public class UserService { @Transactional public boolean addUser(String userName, String password) { try { // call DAO layer and adds to database. } catch (Throwable e) { TransactionAspectSupport.currentTransactionStatus() .setRollbackOnly(); } } public boolean addUsers(Listusers) { for (User user : users) { addUser(user.getUserName, user.getPassword); } } }
他在 addUser 方法上使用 @Transactional 來使用事務功能, 然后他在外部服務中, 通過調用 addUsers 方法批量添加用戶. 經過了上面的分析后, 現在我們就可知道其實這里添加注解是不會啟動事務功能的, 因為 AOP 邏輯整個都沒生效嘛.
解決這個問題的方法有兩個, 一個是使用 AspectJ 模式的事務實現:
另一個就是和我們剛才在上面的例子中的解決方式一樣:
public class UserService { private UserService self; public void setSelf(UserService self) { this.self = self; } @Transactional public boolean addUser(String userName, String password) { try { // call DAO layer and adds to database. } catch (Throwable e) { TransactionAspectSupport.currentTransactionStatus() .setRollbackOnly(); } } public boolean addUsers(Listusers) { for (User user : users) { self.addUser(user.getUserName, user.getPassword); } } }
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/69876.html
摘要:總結動態代理的相關原理已經講解完畢,接下來讓我們回答以下幾個思考題。 【干貨點】 此處是【好好面試】系列文的第12篇文章。文章目標主要是通過原理剖析的方式解答Aop動態代理的面試熱點問題,通過一步步提出問題和了解原理的方式,我們可以記得更深更牢,進而解決被面試官卡住喉嚨的情況。問題如下 SpringBoot默認代理類型是什么 為什么不用靜態代理 JDK動態代理原理 CGLIB動態代理...
摘要:又是什么其實就是一種實現動態代理的技術,利用了開源包,先將代理對象類的文件加載進來,之后通過修改其字節碼并且生成子類。 在實際研發中,Spring是我們經常會使用的框架,畢竟它們太火了,也因此Spring相關的知識點也是面試必問點,今天我們就大話Aop。特地在周末推文,因為該篇文章閱讀起來還是比較輕松詼諧的,當然了,更主要的是周末的我也在充電學習,希望有追求的朋友們也盡量不要放過周末時...
摘要:幾乎每一個接口被調用后,都要記錄一條跟這個參數掛鉤的特定的日志到數據庫。我最終采用了的方式,采取攔截的請求的方式,來記錄日志。所有打上了這個注解的方法,將會記錄日志。那么如何從眾多可能的參數中,為當前的日志指定對應的參數呢。 前言 不久前,因為需求的原因,需要實現一個操作日志。幾乎每一個接口被調用后,都要記錄一條跟這個參數掛鉤的特定的日志到數據庫。舉個例子,就比如禁言操作,日志中需要記...
摘要:會一直完善下去,歡迎建議和指導,同時也歡迎中用到了那些設計模式中用到了那些設計模式這兩個問題,在面試中比較常見。工廠設計模式使用工廠模式可以通過或創建對象。 我自己總結的Java學習的系統知識點以及面試問題,已經開源,目前已經 41k+ Star。會一直完善下去,歡迎建議和指導,同時也歡迎Star: https://github.com/Snailclimb... JDK 中用到了那...
摘要:下圖展示了這些概念的關聯方式通知切面的工作被稱為通知。切面在指定的連接點被織入到目標對象中。該注解表明不僅僅是一個,還是一個切面。 在軟件開發中,散布于應用中多處的功能被稱為橫切關注點(crosscutting concern)。通常來講,這些橫切關注點從概念上是與應用的業務邏輯相分離的(但是往往會直接嵌入到應用的業務邏輯之中)。把這些橫切關注點與業務邏輯相分離正是面向切面編程(AOP...
閱讀 688·2021-11-18 10:07
閱讀 2878·2021-09-22 16:04
閱讀 873·2021-08-16 10:50
閱讀 3326·2019-08-30 15:56
閱讀 1784·2019-08-29 13:22
閱讀 2647·2019-08-26 17:15
閱讀 1229·2019-08-26 10:57
閱讀 1103·2019-08-23 15:23