摘要:起因考慮如下一個例子定義在這個例子中我們定義了一個注解這個是一個方法注解我們的期望是當(dāng)有此注解的方法被調(diào)用時需要執(zhí)行指定的切面邏輯即執(zhí)行方法在類中方法被所注解因此調(diào)用方法時應(yīng)該會觸發(fā)方法的調(diào)用不過有一點(diǎn)我
起因
考慮如下一個例子:
@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, 這個是一個方法注解, 我們的期望是當(dāng)有此注解的方法被調(diào)用時, 需要執(zhí)行指定的切面邏輯, 即執(zhí)行 MyAopAdviseDefine.logMethodInvokeParam 方法.
在 SomeService 類中, 方法 test() 被 MyMonitor 所注解, 因此調(diào)用 test() 方法時, 應(yīng)該會觸發(fā) logMethodInvokeParam 方法的調(diào)用. 不過有一點(diǎn)我們需要注意到, 我們在 MyAopDemo 測試?yán)又? 并沒有直接調(diào)用 SomeService.test() 方法, 而是調(diào)用了 SomeService.hello() 方法, 在 hello 方法中, 調(diào)用了同一個類內(nèi)部的 SomeService.test() 方法. 按理說, test() 方法被調(diào)用時, 會觸發(fā) AOP 邏輯, 但是在這個例子中, 我們并沒有如愿地看到 MyAopAdviseDefine.logMethodInvokeParam 方法的調(diào)用, 這是為什么呢?
這是由于 Spring AOP (包括動態(tài)代理和 CGLIB 的 AOP) 的限制導(dǎo)致的. Spring AOP 并不是擴(kuò)展了一個類(目標(biāo)對象), 而是使用了一個代理對象來包裝目標(biāo)對象, 并攔截目標(biāo)對象的方法調(diào)用. 這樣的實(shí)現(xiàn)帶來的影響是: 在目標(biāo)對象中調(diào)用自己類內(nèi)部實(shí)現(xiàn)的方法時, 這些調(diào)用并不會轉(zhuǎn)發(fā)到代理對象中, 甚至代理對象都不知道有此調(diào)用的存在.
即考慮到上面的代碼中, 我們在 MyAopDemo.aopTest() 中, 調(diào)用了 someService.hello("abc"), 這里的 someService bean 其實(shí)是 Spring AOP 所自動實(shí)例化的一個代理對象, 當(dāng)調(diào)用 hello() 方法時, 先進(jìn)入到此代理對象的同名方法中, 然后在代理對象中執(zhí)行 AOP 邏輯(因?yàn)?hello 方法并沒有注入 AOP 橫切邏輯, 因此調(diào)用它不會有額外的事情發(fā)生), 當(dāng)代理對象中執(zhí)行完畢橫切邏輯后, 才將調(diào)用請求轉(zhuǎn)發(fā)到目標(biāo)對象的 hello() 方法上. 因此當(dāng)代碼執(zhí)行到 hello() 方法內(nèi)部時, 此時的 this 其實(shí)就不是代理對象了, 而是目標(biāo)對象, 因此再調(diào)用 SomeService.test() 自然就沒有 AOP 效果了.
簡單來說, 在 MyAopDemo 中所看到的 someService 這個 bean 和在 SomeService.hello() 方法內(nèi)部上下文中的 this 其實(shí)代表的不是同一個對象(可以通過分別打印兩者的 hashCode 以驗(yàn)證), 前者是 Spring AOP 所生成的代理對象, 而后者才是真正的目標(biāo)對象(SomeService 實(shí)例).
解決弄懂了上面的分析, 那么解決這個問題就十分簡單了. 既然 test() 方法調(diào)用沒有觸發(fā) AOP 邏輯的原因是因?yàn)槲覀円阅繕?biāo)對象的身份(target object) 來調(diào)用的, 那么解決的關(guān)鍵自然就是以代理對象(proxied object)的身份來調(diào)用 test() 方法.
因此針對于上面的例子, 我們進(jìn)行如下修改即可:
@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 字段中(這里再次強(qiáng)調(diào)的是, SomeService bean 實(shí)際上是一個代理對象, 它和 this 引用所指向的對象并不是同一個對象), 因此我們在 hello 方法調(diào)用中, 使用 self.test() 的方式來調(diào)用 test() 方法, 這樣就會觸發(fā) AOP 邏輯了.
Spring AOP 導(dǎo)致的 @Transactional 不生效的問題這個問題同樣地會影響到 @Transactional 注解的使用, 因?yàn)?@Transactional 注解本質(zhì)上也是由 AOP 所實(shí)現(xiàn)的.
例如我在 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 來使用事務(wù)功能, 然后他在外部服務(wù)中, 通過調(diào)用 addUsers 方法批量添加用戶. 經(jīng)過了上面的分析后, 現(xiàn)在我們就可知道其實(shí)這里添加注解是不會啟動事務(wù)功能的, 因?yàn)?AOP 邏輯整個都沒生效嘛.
解決這個問題的方法有兩個, 一個是使用 AspectJ 模式的事務(wù)實(shí)現(xiàn):
另一個就是和我們剛才在上面的例子中的解決方式一樣:
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); } } }
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/69876.html
摘要:總結(jié)動態(tài)代理的相關(guān)原理已經(jīng)講解完畢,接下來讓我們回答以下幾個思考題。 【干貨點(diǎn)】 此處是【好好面試】系列文的第12篇文章。文章目標(biāo)主要是通過原理剖析的方式解答Aop動態(tài)代理的面試熱點(diǎn)問題,通過一步步提出問題和了解原理的方式,我們可以記得更深更牢,進(jìn)而解決被面試官卡住喉嚨的情況。問題如下 SpringBoot默認(rèn)代理類型是什么 為什么不用靜態(tài)代理 JDK動態(tài)代理原理 CGLIB動態(tài)代理...
摘要:又是什么其實(shí)就是一種實(shí)現(xiàn)動態(tài)代理的技術(shù),利用了開源包,先將代理對象類的文件加載進(jìn)來,之后通過修改其字節(jié)碼并且生成子類。 在實(shí)際研發(fā)中,Spring是我們經(jīng)常會使用的框架,畢竟它們太火了,也因此Spring相關(guān)的知識點(diǎn)也是面試必問點(diǎn),今天我們就大話Aop。特地在周末推文,因?yàn)樵撈恼麻喿x起來還是比較輕松詼諧的,當(dāng)然了,更主要的是周末的我也在充電學(xué)習(xí),希望有追求的朋友們也盡量不要放過周末時...
摘要:幾乎每一個接口被調(diào)用后,都要記錄一條跟這個參數(shù)掛鉤的特定的日志到數(shù)據(jù)庫。我最終采用了的方式,采取攔截的請求的方式,來記錄日志。所有打上了這個注解的方法,將會記錄日志。那么如何從眾多可能的參數(shù)中,為當(dāng)前的日志指定對應(yīng)的參數(shù)呢。 前言 不久前,因?yàn)樾枨蟮脑颍枰獙?shí)現(xiàn)一個操作日志。幾乎每一個接口被調(diào)用后,都要記錄一條跟這個參數(shù)掛鉤的特定的日志到數(shù)據(jù)庫。舉個例子,就比如禁言操作,日志中需要記...
摘要:會一直完善下去,歡迎建議和指導(dǎo),同時也歡迎中用到了那些設(shè)計(jì)模式中用到了那些設(shè)計(jì)模式這兩個問題,在面試中比較常見。工廠設(shè)計(jì)模式使用工廠模式可以通過或創(chuàng)建對象。 我自己總結(jié)的Java學(xué)習(xí)的系統(tǒng)知識點(diǎn)以及面試問題,已經(jīng)開源,目前已經(jīng) 41k+ Star。會一直完善下去,歡迎建議和指導(dǎo),同時也歡迎Star: https://github.com/Snailclimb... JDK 中用到了那...
摘要:下圖展示了這些概念的關(guān)聯(lián)方式通知切面的工作被稱為通知。切面在指定的連接點(diǎn)被織入到目標(biāo)對象中。該注解表明不僅僅是一個,還是一個切面。 在軟件開發(fā)中,散布于應(yīng)用中多處的功能被稱為橫切關(guān)注點(diǎn)(crosscutting concern)。通常來講,這些橫切關(guān)注點(diǎn)從概念上是與應(yīng)用的業(yè)務(wù)邏輯相分離的(但是往往會直接嵌入到應(yīng)用的業(yè)務(wù)邏輯之中)。把這些橫切關(guān)注點(diǎn)與業(yè)務(wù)邏輯相分離正是面向切面編程(AOP...
閱讀 693·2021-11-18 10:07
閱讀 2884·2021-09-22 16:04
閱讀 885·2021-08-16 10:50
閱讀 3352·2019-08-30 15:56
閱讀 1791·2019-08-29 13:22
閱讀 2679·2019-08-26 17:15
閱讀 1239·2019-08-26 10:57
閱讀 1114·2019-08-23 15:23