SpringAOP四種通知類型+環(huán)繞通知說明
SpringAOP的四種通知類型:前置通知、異常通知、后置通知、異常通知
一、四種常見的通知類型
給出 賬戶的業(yè)務(wù)層接口 IAccountService.java,
為了便于演示這四種通知類型,我們就只留下了一個(gè)方法。
public interface IAccountService { void saveAccount(); }
給出 賬戶的業(yè)務(wù)層接口的實(shí)現(xiàn)類 AccountServiceImpl.java
public class AccountServiceImpl implements IAccountService{ @Override public void saveAccount() { System.out.println("執(zhí)行了保存"); //int i=1/0; } }
給出一個(gè)日志類, 用于打印日志
public class Logger { /** * 前置通知 */ public void beforePrintLog(){ System.out.println("前置通知Logger類中的beforePrintLog方法開始記錄日志了。。。"); } /** * 后置通知 */ public void afterReturningPrintLog(){ System.out.println("后置通知Logger類中的afterReturningPrintLog方法開始記錄日志了。。。"); } /** * 異常通知 */ public void afterThrowingPrintLog(){ System.out.println("異常通知Logger類中的afterThrowingPrintLog方法開始記錄日志了。。。"); } /** * 最終通知 */ public void afterPrintLog(){ System.out.println("最終通知Logger類中的afterPrintLog方法開始記錄日志了。。。"); } }
給出配置信息bean.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- 配置srping的Ioc,把service對(duì)象配置進(jìn)來--> <bean id="accountService" class="service.AccountServiceImpl"></bean> <!-- 配置Logger類 --> <bean id="logger" class="utils.Logger"></bean> <!--配置AOP--> <aop:config> <!--配置切入點(diǎn)表達(dá)式 --> <aop:pointcut id="pt1" expression="execution(* service.AccountServiceImpl.saveAccount())"></aop:pointcut> <!--配置切面 --> <aop:aspect id="logAdvice" ref="logger"> <!-- 配置前置通知:在切入點(diǎn)方法執(zhí)行之前執(zhí)行--> <aop:before method="beforePrintLog" pointcut-ref="pt1" ></aop:before> <!-- 配置后置通知:在切入點(diǎn)方法正常執(zhí)行之后值--> <aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1"></aop:after-returning> <!-- 配置異常通知:在切入點(diǎn)方法執(zhí)行產(chǎn)生異常之后執(zhí)行--> <aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing> <!-- 配置最終通知:無(wú)論切入點(diǎn)方法是否正常執(zhí)行它都會(huì)在其后面執(zhí)行--> <aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after> </aop:aspect> </aop:config> </beans>
注意
1)異常通知和后置通知永遠(yuǎn)只能執(zhí)行一個(gè)
2)配置切入點(diǎn)表達(dá)式
此標(biāo)簽寫在aop:aspect標(biāo)簽內(nèi)部只能當(dāng)前切面使用。
它還可以寫在aop:aspect外面,此時(shí)就變成了所有切面可用
給出Test類
public class AOPTest { public static void main(String[] args) { //1.獲取容器 ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); //2.獲取對(duì)象 IAccountService as = (IAccountService)ac.getBean("accountService"); //3.執(zhí)行方法 as.saveAccount(); } }
執(zhí)行結(jié)果:
當(dāng)我們放開AccountServiceImpl類中我們故意制造的異常 int i=1/0;時(shí):
二、環(huán)繞通知
環(huán)繞通知,只需要稍稍微改變上面例子的兩點(diǎn)即可
1、改動(dòng)日志類 Logger.java
public class Logger { public Object aroundPringLog(ProceedingJoinPoint pjp){ Object rtValue = null; try{ Object[] args = pjp.getArgs();//得到方法執(zhí)行所需的參數(shù) System.out.println("Logger類中的aroundPringLog方法開始記錄日志了。。。前置"); rtValue = pjp.proceed(args);//明確調(diào)用業(yè)務(wù)層方法(切入點(diǎn)方法) System.out.println("Logger類中的aroundPringLog方法開始記錄日志了。。。后置"); return rtValue; }catch (Throwable t){ System.out.println("Logger類中的aroundPringLog方法開始記錄日志了。。。異常"); throw new RuntimeException(t); }finally { System.out.println("Logger類中的aroundPringLog方法開始記錄日志了。。。最終"); } } }
注意:pjp.proceed(args)會(huì)報(bào)異常,必須用 Throwable t,因?yàn)镋xception攔不住它
2、改動(dòng)配置文件
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- 配置srping的Ioc,把service對(duì)象配置進(jìn)來--> <bean id="accountService" class="service.AccountServiceImpl"></bean> <!-- 配置Logger類 --> <bean id="logger" class="utils.Logger"></bean> <!--配置AOP--> <aop:config> <!--配置切入點(diǎn)表達(dá)式 --> <aop:pointcut id="pt1" expression="execution(* service.AccountServiceImpl.saveAccount())"></aop:pointcut> <!--配置切面 --> <aop:aspect id="logAdvice" ref="logger"> <!-- 配置環(huán)繞通知 詳細(xì)的注釋請(qǐng)看Logger類中--> <aop:around method="aroundPringLog" pointcut-ref="pt1"></aop:around> </aop:aspect> </aop:config> </beans>
分析
- spring中的環(huán)繞通知是spring框架為我們提供的一種可以在代碼中手動(dòng)控制增強(qiáng)方法何時(shí)執(zhí)行的方式。
- Spring框架為我們提供了一個(gè)接口:ProceedingJoinPoint。該接口有一個(gè)方法proceed(),此方法就相當(dāng)于明確調(diào)用切入點(diǎn)方法。該接口可以作為環(huán)繞通知的方法參數(shù),在程序執(zhí)行時(shí),spring框架會(huì)為我們提供該接口的實(shí)現(xiàn)類供我們使用。
AOP機(jī)制之環(huán)繞通知的見解
我們都知道,AOP機(jī)制的核心是在不修改源碼的基礎(chǔ)上對(duì)業(yè)務(wù)層方法的增強(qiáng)。
其中有五個(gè)通知類型
- 1、前置通知:在切入點(diǎn)方法之前執(zhí)行
- 2、后置通知:在切入點(diǎn)方法之后通知
- 3、異常通知:在執(zhí)行切入點(diǎn)方法過程中出現(xiàn)異常后執(zhí)行(因此異常通知和后置通知只能執(zhí)行一個(gè))
- 4、最終通知:無(wú)論切入點(diǎn)方法是否正常執(zhí)行它都會(huì)執(zhí)行
- 5、環(huán)繞通知: 當(dāng)配置環(huán)繞通知之后,在環(huán)繞通知里面必須要明確調(diào)用業(yè)務(wù)層的方法,如果不調(diào)用,就會(huì)出現(xiàn)只出現(xiàn)通知,而不執(zhí)行方法。其中原理和動(dòng)態(tài)代理是一樣的,代碼如下。
public AccountService getAccountService() { return (AccountService) Proxy.newProxyInstance(accountService.getClass().getClassLoader(), accountService.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object o, Method method, Object[] objects) throws Throwable { Object rtValue=null; try { //1、開啟事務(wù) txManager.beginTransaction(); //2、執(zhí)行操作,整個(gè)過程像對(duì)每個(gè)方法進(jìn)行了包裝,并返回新的accountService對(duì)象 rtValue=method.invoke(accountService,objects); //3、提交事務(wù) txManager.commit(); //4、返回結(jié)果 return rtValue; }catch (Exception e){ //5、回滾事務(wù) txManager.rollback(); throw new RuntimeException(e); }finally { //6、釋放連接 txManager.release(); } } }); }
如果不明確調(diào)用業(yè)務(wù)層方法,就像一個(gè)畫皮,沒有發(fā)揮本質(zhì)的作用。
除此之外,我認(rèn)為環(huán)繞通知可以代替其他的四個(gè)通知,
public Object aroundPrintLog(ProceedingJoinPoint pjp){//環(huán)繞通知是不是能夠代替其他的通知 Object rtvalue=null; try { /** *這里的一切都是為都是給業(yè)務(wù)層方法進(jìn)行增強(qiáng),例如:把那些方法拿過來,然后核心的還是rtvalue=pjp.proceed(args), *其他的輸出只不過是為核心的業(yè)務(wù)層方法進(jìn)行修飾 */ Object[] args=pjp.getArgs();//得到方法執(zhí)行所需的參數(shù) System.out.println("前置通知"); rtvalue=pjp.proceed(args);//明確調(diào)用業(yè)務(wù)層方法 System.out.println("后置通知"); return rtvalue; }catch (Throwable e){ System.out.println("異常通知"); throw new RuntimeException(e); }finally { System.out.println("最終通知"); } }
這個(gè)屬于典型的環(huán)繞通知,其中把輸出方法換成相應(yīng)的通知方法就可以(有不同觀點(diǎn)的可以說出來一起討論)。
最后,分享一下我學(xué)這個(gè)知識(shí)的方法。方法增強(qiáng),本質(zhì)上就是對(duì)源碼不修改的情況下進(jìn)行方法的加工。就好像烤羊肉串一樣,其中的核心就是羊肉,就像公司給你的業(yè)務(wù)層方法(這個(gè)是不讓修改的)。在給顧客使用前,收先對(duì)羊肉進(jìn)行刷油、烤、撒料一系列過程,這一過程就是我們對(duì)業(yè)務(wù)層方法的增強(qiáng),使業(yè)務(wù)層的功能更加健壯,對(duì)應(yīng)的燒烤也就是更美味。核心的一點(diǎn)就是正確調(diào)用業(yè)務(wù)層的方法,不管在哪類通知中,都能對(duì)業(yè)務(wù)層方法進(jìn)行正確、有效地增強(qiáng)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
springboot+quartz以持久化的方式實(shí)現(xiàn)定時(shí)任務(wù)的代碼
這篇文章主要介紹了springboot+quartz以持久化的方式實(shí)現(xiàn)定時(shí)任務(wù)的相關(guān)知識(shí),本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-07-07基于Springboot的漫畫網(wǎng)站平臺(tái)設(shè)計(jì)與實(shí)現(xiàn)
本文將基于Springboot實(shí)現(xiàn)開發(fā)一個(gè)漫畫主題的網(wǎng)站,實(shí)現(xiàn)一個(gè)比漂亮的動(dòng)漫連載的網(wǎng)站系統(tǒng),界面設(shè)計(jì)優(yōu)雅大方,比較適合做畢業(yè)設(shè)計(jì)和課程設(shè)計(jì)使用,需要的可以參考一下2022-08-08Java優(yōu)雅的處理金錢問題(BigDecimal)
本文主要介紹了Java優(yōu)雅的處理金錢問題(BigDecimal),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-06-06Java中IO流文件讀取、寫入和復(fù)制的實(shí)例
下面小編就為大家?guī)硪黄狫ava中IO流文件讀取、寫入和復(fù)制的實(shí)例。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-10-10簡(jiǎn)單了解JAVA中類、實(shí)例與Class對(duì)象
這篇文章主要介紹了簡(jiǎn)單了解JAVA中類、實(shí)例與Class對(duì)象,類是面向?qū)ο缶幊陶Z(yǔ)言的一個(gè)重要概念,它是對(duì)一項(xiàng)事物的抽象概括,可以包含該事物的一些屬性定義,以及操作屬性的方法,需要的朋友可以參考下2019-06-06springboot之如何同時(shí)連接多個(gè)redis
這篇文章主要介紹了springboot之如何同時(shí)連接多個(gè)redis問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-04-04