SpringAOP四種通知類型+環(huán)繞通知說明
SpringAOP的四種通知類型:前置通知、異常通知、后置通知、異常通知
一、四種常見的通知類型
給出 賬戶的業(yè)務(wù)層接口 IAccountService.java,
為了便于演示這四種通知類型,我們就只留下了一個方法。
public interface IAccountService {
void saveAccount();
}
給出 賬戶的業(yè)務(wù)層接口的實現(xiàn)類 AccountServiceImpl.java
public class AccountServiceImpl implements IAccountService{
@Override
public void saveAccount() {
System.out.println("執(zhí)行了保存");
//int i=1/0;
}
}
給出一個日志類, 用于打印日志
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對象配置進(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>
<!-- 配置最終通知:無論切入點(diǎn)方法是否正常執(zhí)行它都會在其后面執(zhí)行-->
<aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after>
</aop:aspect>
</aop:config>
</beans>
注意
1)異常通知和后置通知永遠(yuǎn)只能執(zhí)行一個
2)配置切入點(diǎn)表達(dá)式
此標(biāo)簽寫在aop:aspect標(biāo)簽內(nèi)部只能當(dāng)前切面使用。
它還可以寫在aop:aspect外面,此時就變成了所有切面可用
給出Test類
public class AOPTest {
public static void main(String[] args) {
//1.獲取容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.獲取對象
IAccountService as = (IAccountService)ac.getBean("accountService");
//3.執(zhí)行方法
as.saveAccount();
}
}
執(zhí)行結(jié)果:

當(dāng)我們放開AccountServiceImpl類中我們故意制造的異常 int i=1/0;時:

二、環(huán)繞通知
環(huán)繞通知,只需要稍稍微改變上面例子的兩點(diǎn)即可
1、改動日志類 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)會報異常,必須用 Throwable t,因為Exception攔不住它
2、改動配置文件
<?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對象配置進(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ì)的注釋請看Logger類中-->
<aop:around method="aroundPringLog" pointcut-ref="pt1"></aop:around>
</aop:aspect>
</aop:config>
</beans>
分析
- spring中的環(huán)繞通知是spring框架為我們提供的一種可以在代碼中手動控制增強(qiáng)方法何時執(zhí)行的方式。
- Spring框架為我們提供了一個接口:ProceedingJoinPoint。該接口有一個方法proceed(),此方法就相當(dāng)于明確調(diào)用切入點(diǎn)方法。該接口可以作為環(huán)繞通知的方法參數(shù),在程序執(zhí)行時,spring框架會為我們提供該接口的實現(xiàn)類供我們使用。
AOP機(jī)制之環(huán)繞通知的見解
我們都知道,AOP機(jī)制的核心是在不修改源碼的基礎(chǔ)上對業(yè)務(wù)層方法的增強(qiáng)。
其中有五個通知類型
- 1、前置通知:在切入點(diǎn)方法之前執(zhí)行
- 2、后置通知:在切入點(diǎn)方法之后通知
- 3、異常通知:在執(zhí)行切入點(diǎn)方法過程中出現(xiàn)異常后執(zhí)行(因此異常通知和后置通知只能執(zhí)行一個)
- 4、最終通知:無論切入點(diǎn)方法是否正常執(zhí)行它都會執(zhí)行
- 5、環(huán)繞通知: 當(dāng)配置環(huán)繞通知之后,在環(huán)繞通知里面必須要明確調(diào)用業(yè)務(wù)層的方法,如果不調(diào)用,就會出現(xiàn)只出現(xiàn)通知,而不執(zhí)行方法。其中原理和動態(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í)行操作,整個過程像對每個方法進(jìn)行了包裝,并返回新的accountService對象
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ù)層方法,就像一個畫皮,沒有發(fā)揮本質(zhì)的作用。
除此之外,我認(rèn)為環(huán)繞通知可以代替其他的四個通知,
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("最終通知");
}
}
這個屬于典型的環(huán)繞通知,其中把輸出方法換成相應(yīng)的通知方法就可以(有不同觀點(diǎn)的可以說出來一起討論)。
最后,分享一下我學(xué)這個知識的方法。方法增強(qiáng),本質(zhì)上就是對源碼不修改的情況下進(jìn)行方法的加工。就好像烤羊肉串一樣,其中的核心就是羊肉,就像公司給你的業(yè)務(wù)層方法(這個是不讓修改的)。在給顧客使用前,收先對羊肉進(jìn)行刷油、烤、撒料一系列過程,這一過程就是我們對業(yè)務(wù)層方法的增強(qiáng),使業(yè)務(wù)層的功能更加健壯,對應(yīng)的燒烤也就是更美味。核心的一點(diǎn)就是正確調(diào)用業(yè)務(wù)層的方法,不管在哪類通知中,都能對業(yè)務(wù)層方法進(jìn)行正確、有效地增強(qiáng)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
spring事務(wù)之事務(wù)掛起和事務(wù)恢復(fù)源碼解讀
這篇文章主要介紹了spring事務(wù)之事務(wù)掛起和事務(wù)恢復(fù)源碼解讀,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-11-11
Java自帶的Http?Server實現(xiàn)設(shè)置返回值的類型(content-type)
這篇文章主要介紹了Java自帶的Http?Server實現(xiàn)設(shè)置返回值的類型(content-type),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-11-11
IDEA運(yùn)行Tomcat中文亂碼出現(xiàn)的各種問題
這篇文章主要介紹了IDEA運(yùn)行Tomcat中文亂碼的各種問題及解決方案,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-11-11
基于SpringMVC中的路徑參數(shù)和URL參數(shù)實例
這篇文章主要介紹了基于SpringMVC中的路徑參數(shù)和URL參數(shù)實例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-02-02
SpringBoot整合Netty+Websocket實現(xiàn)消息推送的示例代碼
WebSocket使得客戶端和服務(wù)器之間的數(shù)據(jù)交換變得更加簡單,允許服務(wù)端主動向客戶端推送數(shù)據(jù),本文主要介紹了SpringBoot整合Netty+Websocket實現(xiàn)消息推送的示例代碼,具有一定的參考價值,感興趣的可以了解一下2024-01-01
深入理解Java8新特性之Stream API的終止操作步驟
Stream是Java8的一大亮點(diǎn),是對容器對象功能的增強(qiáng),它專注于對容器對象進(jìn)行各種非常便利、高效的 聚合操作(aggregate operation)或者大批量數(shù)據(jù)操作。Stream API借助于同樣新出現(xiàn)的Lambda表達(dá)式,極大的提高編程效率和程序可讀性,感興趣的朋友快來看看吧2021-11-11

