詳解Spring中的AOP及AspectJ五大通知注解
AOP 基本概念
AOP(Aspect-Oriented Programming, 面向切面編程): 是一種新的方法論,是對傳統(tǒng) -OOP(Object-Oriented Programming, 面向?qū)ο缶幊? 的補充。
AOP 的主要編程對象是切面(aspect), 切面模塊化橫切關(guān)注點。
在應(yīng)用 AOP 編程時, 仍然需要定義公共功能, 但可以明確的定義這個功能用在哪里,以什么方式應(yīng)用,并且不必修改受影響的類,這樣一來橫切關(guān)注點就被模塊化到特殊的對象(切面)里。
AOP 的好處:
- 每個事物邏輯位于一個位置,代碼不分散,便于維護(hù)和升級。
- 業(yè)務(wù)模塊更簡潔,只包含核心業(yè)務(wù)代碼。
AOP術(shù)語
- 切面(Aspect): 橫切關(guān)注點(跨越應(yīng)用程序多個模塊的功能)被模塊化的特殊對象。
- 通知(Advice): 切面必須要完成的工作。
- 目標(biāo)(Target): 被通知的對象。
- 代理(Proxy): 向目標(biāo)對象應(yīng)用通知之后創(chuàng)建的對象。
- 連接點(Joinpoint):程序執(zhí)行的某個特定位置:如類某個方法調(diào)用前、調(diào)用后、方法拋出異常后等。連接點由兩個信息確定:方法表示的程序執(zhí)行點;相對點表示的方位。方位為方法執(zhí)行前的位置。
- 切點(pointcut):每個類都擁有多個連接點:即連接點是程序類中客觀存在的事務(wù)。AOP 通過切點定位到特定的連接點。類比:連接點相當(dāng)于數(shù)據(jù)庫中的記錄,切點相當(dāng)于查詢條件。切點和連接點不是一對一的關(guān)系,一個切點匹配多個連接點,切點通過
org.springframework.aop.Pointcut
接口進(jìn)行描述,它使用類和方法作為連接點的查詢條件。
AspectJ
- AspectJ:Java 社區(qū)里最完整最流行的 AOP 框架.
- 在 Spring2.0 以上版本中,可以使用基于 AspectJ 注解或基于 XML 配置的 AOP
應(yīng)用
本文以一個簡單計算器為代碼例子
1、在Spring中啟用AspectJ 注解支持
- 要在 Spring 應(yīng)用中使用 AspectJ 注解, 必須在 classpath 下包含 AspectJ 類庫: aopalliance.jar、aspectj.weaver.jar 和 spring-aspects.jar
- 將aopSchema添加到 beans根元素中
- 要在 Spring IOC 容器中啟用 AspectJ 注解支持,只要在Bean配置文件中定義一個空的XML元素<aop:aspectj-autoproxy>
- 當(dāng) Spring IOC 容器偵測到 Bean 配置文件中的 <aop:aspectj-autoproxy> 元素時,會自動為與 AspectJ 切面匹配的 Bean 創(chuàng)建代理
2、用 AspectJ 注解聲明切面
- 要在Spring中聲明AspectJ切面只需要在IOC容器中將切面聲明為Bean實例。當(dāng)在 Spring IOC 容器中初始化 AspectJ 切面之后,Spring IOC 容器就會為那些與 AspectJ 切面相匹配的 Bean 創(chuàng)建代理。
- 在AspectJ注解中, 切面只是一個帶有
@Aspect
注解的Java類。 - 通知是標(biāo)注有某種注解的簡單的Java方法。
- AspectJ 支持 5 種類型的通知注解:
- @Before: 前置通知,在方法執(zhí)行之前執(zhí)行
- @After: 后置通知,在方法執(zhí)行之后執(zhí)行
- @AfterRunning:返回通知,在方法返回結(jié)果之后執(zhí)行
- @AfterThrowing: 異常通知,在方法拋出異常之后
- @Around: 環(huán)繞通知,圍繞著方法執(zhí)行
前置通知
- 前置通知:在方法執(zhí)行之前執(zhí)行的通知。
- 前置通知使用
@Before
注解, 并將切入點表達(dá)式的值作為注解值。
后置通知
- 后置通知是在連接點完成之后執(zhí)行的,即連接點返回結(jié)果或者拋出異常的時候,下面的后置通知記錄了方法的終止。
- 一個切面可以包括一個或者多個通知。
返回通知
- 無論連接點是正常返回還是拋出異常,后置通知都會執(zhí)行。如果只想在連接點返回的時候記錄日志, 應(yīng)使用返回通知代替后置通知。
- 在返回通知中, 只要將 returning 屬性添加到 @AfterReturning 注解中, 就可以訪問連接點的返回值。 該屬性的值即為用來傳入返回值的參數(shù)名稱。
- 必須在通知方法的簽名中添加一個同名參數(shù)。在運行時,Spring AOP 會通過這個參數(shù)傳遞返回值。
- 原始的切點表達(dá)式需要出現(xiàn)在pointcut屬性中。
異常通知
- 只在連接點拋出異常時才執(zhí)行異常通知。
- 將Throwing屬性添加到
@AfterThrowing
注解中, 也可以訪問連接點拋出的異常. Throwable 是所有錯誤和異常類的超類,所以在異常通知方法可以捕獲到任何錯誤和異常。 - 如果只對某種特殊的異常類型感興趣, 可以將參數(shù)聲明為其他異常的參數(shù)類型, 然后通知就只在拋出這個類型及其子類的異常時才被執(zhí)行。
環(huán)繞通知
- 環(huán)繞通知是所有通知類型中功能最為強大的, 能夠全面地控制連接點, 甚至可以控制是否執(zhí)行連接點。
- 對于環(huán)繞通知來說,連接點的參數(shù)類型必須是ProceedingJoinPoint ,它是 JoinPoint 的子接口,允許控制何時執(zhí)行,是否執(zhí)行連接點。
- 在環(huán)繞通知中需要明確調(diào)用ProceedingJoinPoint的proceed()方法來執(zhí)行被代理的方法.如果忘記這樣做就會導(dǎo)致通知被執(zhí)行了,但目標(biāo)方法沒有被執(zhí)行。
- 注意: 環(huán)繞通知的方法需要返回目標(biāo)方法執(zhí)行之后的結(jié)果,即調(diào)用 joinPoint.proceed()的返回值,否則會出現(xiàn)空指針異常。
實現(xiàn)代碼
1、Calc.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:context="http://www.springframework.org/schema/context" 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/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <context:component-scan base-package="com.ycfxhsw.aop"></context:component-scan> <!-- 使用AspactJ,注解起作用:自動為匹配的類生成代理對象 --> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> </beans>
2、Calc接口
public interface Calc { // 加法 int Addition(int i, int j); // 減法 int Subtraction(int i, int j); // 乘法 int Multiplication(int i, int j); // 除法 int Division(int i, int j); }
3、接口實現(xiàn)CalcImp
import org.springframework.stereotype.Component; @Component public class CalcImp implements Calc { public int Addition(int i, int j) { int result = i + j; return result; } public int Subtraction(int i, int j) { int result = i + j; return result; } public int Multiplication(int i, int j) { int result = i + j; return result; } public int Division(int i, int j) { int result = i + j; return result; } }
4、LoggingAspect
import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import java.util.Arrays; import java.util.List; // @Order(1) 指定切面優(yōu)先級 @Component @Aspect public class LoggingAspect { /** * 申明切入點表達(dá)式,一般該方法內(nèi)不需要添加其他方法 * 使用 @Pointcut 申明切入點表達(dá)式 * 后面的切入點直接使用方法名 */ @Pointcut("execution(public int com.ycfxhsw.aop.Calc.*(..))") public void DeclareJoinPointExpression() { } /** * 前置通知 * @param joinPoint */ @Before("execution(public int com.ycfxhsw.aop.Calc.*(int, int))") public void BeforeMethod(JoinPoint joinPoint) { String MethodName = joinPoint.getSignature().getName(); List<Object> list = Arrays.asList(joinPoint.getArgs()); System.out.println("Method Starts..." + MethodName + " with " + list); } /** * 后置通知(無論有無異常) * 不能訪問目標(biāo)方法的執(zhí)行結(jié)果 * @param joinPoint * com.ycfxhsw.aop.Calc.*(..) * 重用 DeclareJoinPointExpression() */ @After("DeclareJoinPointExpression()") public void AfterMethod(JoinPoint joinPoint) { String MethodName = joinPoint.getSignature().getName(); System.out.println("Method Ends..." + MethodName); } /** * 返回通知 * 在方法正常結(jié)束后執(zhí)行 * 可以訪問目標(biāo)方法的執(zhí)行結(jié)果 * @param joinPoint,result */ @AfterReturning(value = "execution(public int com.ycfxhsw.aop.Calc.*(..))", returning = "result") public void AfterReturningMethod(JoinPoint joinPoint, Object result) { String MethodName = joinPoint.getSignature().getName(); System.out.println("Method AfterReturning..." + MethodName + " --> " + result); } /** * 異常通知 * @param joinPoint,exception */ @AfterThrowing(value = "execution(public int com.ycfxhsw.aop.Calc.*(..))", throwing = "exception") public void AfterThrowingMethod(JoinPoint joinPoint, Exception exception) { String MethodName = joinPoint.getSignature().getName(); System.out.println("Method AfterThrowing..." + MethodName + " --> " + exception); } /** * 環(huán)繞通知,需要攜帶 ProceedingJoinPoint 類型的參數(shù) * 類似于動態(tài)代理全過程 * 必須有返回值(目標(biāo)方法返回值) * @param proceedingJoinPoint @Around("execution(public int com.ycfxhsw.aop.Calc.*(..))") public Object AroundMethod(ProceedingJoinPoint proceedingJoinPoint) { Object result = null; String methodName = proceedingJoinPoint.getSignature().getName(); try { // 前置通知 System.out.println("The method " + methodName + " begins with " + Arrays.asList(proceedingJoinPoint.getArgs())); // 執(zhí)行目標(biāo)方法 result = proceedingJoinPoint.proceed(); // 返回通知 System.out.println("The method " + methodName + " ends with " + result); } catch (Throwable e) { // 異常通知 System.out.println("The method " + methodName + " occurs exception:" + e); throw new RuntimeException(e); } // 后置通知 System.out.println("The method " + methodName + " ends"); return result; } */ }
5、測試Main
import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; /** * 測試 */ public class Main { public static void main(String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("Calc.xml"); Calc calc = applicationContext.getBean(Calc.class); System.out.println(calc.getClass().getName()); int resultAdd = calc.Addition(5, 6); System.out.println("result-->" + resultAdd + "\n"); int resultSub = calc.Subtraction(9, 3); System.out.println("result-->" + resultSub + "\n"); int resultMul = calc.Multiplication(8, 6); System.out.println("result-->" + resultMul + "\n"); int resultDiv = calc.Division(99, 3); System.out.println("result-->" + resultDiv + "\n"); } }
3、指定切面的優(yōu)先級
- 在同一個連接點上應(yīng)用不止一個切面時,除非明確指定,否則它們的優(yōu)先級是不確定的.
- 切面的優(yōu)先級可以通過實現(xiàn) Ordered 接口或利用
@Order
注解指定. - 實現(xiàn) Ordered 接口,
getOrder()
方法的返回值越小, 優(yōu)先級越高. - 若使用
@Order
注解,序號出現(xiàn)在注解中
4、重用切入點定義
- 在編寫 AspectJ 切面時,可以直接在通知注解中書寫切入點表達(dá)式,但同一個切點表達(dá)式可能會在多個通知中重復(fù)出現(xiàn)。
- 在 AspectJ 切面中, 可以通過 @Pointcut注解將一個切入點聲明成簡單的方法,切入點的方法體通常是空的,因為將切入點定義與應(yīng)用程序邏輯混在一起是不合理的。
- 切入點方法的訪問控制符同時也控制著這個切入點的可見性,如果切入點要在多個切面中共用, 最好將它們集中在一個公共的類中,在這種情況下, 它們必須被聲明為 public。在引入這個切入點時,必須將類名也包括在內(nèi),如果類沒有與這個切面放在同一個包中, 還必須包含包名。
- 其他通知可以通過方法名稱引入該切入點。
到此這篇關(guān)于詳解Spring中的AOP及AspectJ五大通知注解的文章就介紹到這了,更多相關(guān)Spring的AOP及AspectJ內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring MVC+MyBatis+MySQL實現(xiàn)分頁功能實例
分頁功能是我們?nèi)粘i_發(fā)中經(jīng)常會遇到的,下面這篇文章主要給大家介紹了Spring MVC+MyBatis+MySQL實現(xiàn)分頁功能的相關(guān)資料,文中介紹的非常詳細(xì),對大家具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起看看吧。2017-06-06Java調(diào)用IK分詞器進(jìn)行分詞方式,封裝工具類
這篇文章主要介紹了Java調(diào)用IK分詞器進(jìn)行分詞方式,封裝工具類,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-08-08springboot如何開啟一個監(jiān)聽線程執(zhí)行任務(wù)
這篇文章主要介紹了springboot如何開啟一個監(jiān)聽線程執(zhí)行任務(wù)問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-02-02Java基礎(chǔ)之多線程方法狀態(tài)和創(chuàng)建方法
Java中可以通過Thread類和Runnable接口來創(chuàng)建多個線程,下面這篇文章主要給大家介紹了關(guān)于Java基礎(chǔ)之多線程方法狀態(tài)和創(chuàng)建方法的相關(guān)資料,需要的朋友可以參考下2021-09-09