詳解Spring中的AOP及AspectJ五大通知注解
AOP 基本概念
AOP(Aspect-Oriented Programming, 面向切面編程): 是一種新的方法論,是對傳統(tǒng) -OOP(Object-Oriented Programming, 面向對象編程) 的補充。
AOP 的主要編程對象是切面(aspect), 切面模塊化橫切關注點。
在應用 AOP 編程時, 仍然需要定義公共功能, 但可以明確的定義這個功能用在哪里,以什么方式應用,并且不必修改受影響的類,這樣一來橫切關注點就被模塊化到特殊的對象(切面)里。
AOP 的好處:
- 每個事物邏輯位于一個位置,代碼不分散,便于維護和升級。
- 業(yè)務模塊更簡潔,只包含核心業(yè)務代碼。
AOP術語
- 切面(Aspect): 橫切關注點(跨越應用程序多個模塊的功能)被模塊化的特殊對象。
- 通知(Advice): 切面必須要完成的工作。
- 目標(Target): 被通知的對象。
- 代理(Proxy): 向目標對象應用通知之后創(chuàng)建的對象。
- 連接點(Joinpoint):程序執(zhí)行的某個特定位置:如類某個方法調用前、調用后、方法拋出異常后等。連接點由兩個信息確定:方法表示的程序執(zhí)行點;相對點表示的方位。方位為方法執(zhí)行前的位置。
- 切點(pointcut):每個類都擁有多個連接點:即連接點是程序類中客觀存在的事務。AOP 通過切點定位到特定的連接點。類比:連接點相當于數據庫中的記錄,切點相當于查詢條件。切點和連接點不是一對一的關系,一個切點匹配多個連接點,切點通過
org.springframework.aop.Pointcut接口進行描述,它使用類和方法作為連接點的查詢條件。
AspectJ
- AspectJ:Java 社區(qū)里最完整最流行的 AOP 框架.
- 在 Spring2.0 以上版本中,可以使用基于 AspectJ 注解或基于 XML 配置的 AOP
應用
本文以一個簡單計算器為代碼例子

1、在Spring中啟用AspectJ 注解支持
- 要在 Spring 應用中使用 AspectJ 注解, 必須在 classpath 下包含 AspectJ 類庫: aopalliance.jar、aspectj.weaver.jar 和 spring-aspects.jar
- 將aopSchema添加到 beans根元素中
- 要在 Spring IOC 容器中啟用 AspectJ 注解支持,只要在Bean配置文件中定義一個空的XML元素<aop:aspectj-autoproxy>
- 當 Spring IOC 容器偵測到 Bean 配置文件中的 <aop:aspectj-autoproxy> 元素時,會自動為與 AspectJ 切面匹配的 Bean 創(chuàng)建代理
2、用 AspectJ 注解聲明切面
- 要在Spring中聲明AspectJ切面只需要在IOC容器中將切面聲明為Bean實例。當在 Spring IOC 容器中初始化 AspectJ 切面之后,Spring IOC 容器就會為那些與 AspectJ 切面相匹配的 Bean 創(chuàng)建代理。
- 在AspectJ注解中, 切面只是一個帶有
@Aspect注解的Java類。 - 通知是標注有某種注解的簡單的Java方法。
- AspectJ 支持 5 種類型的通知注解:
- @Before: 前置通知,在方法執(zhí)行之前執(zhí)行
- @After: 后置通知,在方法執(zhí)行之后執(zhí)行
- @AfterRunning:返回通知,在方法返回結果之后執(zhí)行
- @AfterThrowing: 異常通知,在方法拋出異常之后
- @Around: 環(huán)繞通知,圍繞著方法執(zhí)行
前置通知
- 前置通知:在方法執(zhí)行之前執(zhí)行的通知。
- 前置通知使用
@Before注解, 并將切入點表達式的值作為注解值。
后置通知
- 后置通知是在連接點完成之后執(zhí)行的,即連接點返回結果或者拋出異常的時候,下面的后置通知記錄了方法的終止。
- 一個切面可以包括一個或者多個通知。
返回通知
- 無論連接點是正常返回還是拋出異常,后置通知都會執(zhí)行。如果只想在連接點返回的時候記錄日志, 應使用返回通知代替后置通知。
- 在返回通知中, 只要將 returning 屬性添加到 @AfterReturning 注解中, 就可以訪問連接點的返回值。 該屬性的值即為用來傳入返回值的參數名稱。
- 必須在通知方法的簽名中添加一個同名參數。在運行時,Spring AOP 會通過這個參數傳遞返回值。
- 原始的切點表達式需要出現(xiàn)在pointcut屬性中。
異常通知
- 只在連接點拋出異常時才執(zhí)行異常通知。
- 將Throwing屬性添加到
@AfterThrowing注解中, 也可以訪問連接點拋出的異常. Throwable 是所有錯誤和異常類的超類,所以在異常通知方法可以捕獲到任何錯誤和異常。 - 如果只對某種特殊的異常類型感興趣, 可以將參數聲明為其他異常的參數類型, 然后通知就只在拋出這個類型及其子類的異常時才被執(zhí)行。
環(huán)繞通知
- 環(huán)繞通知是所有通知類型中功能最為強大的, 能夠全面地控制連接點, 甚至可以控制是否執(zhí)行連接點。
- 對于環(huán)繞通知來說,連接點的參數類型必須是ProceedingJoinPoint ,它是 JoinPoint 的子接口,允許控制何時執(zhí)行,是否執(zhí)行連接點。
- 在環(huán)繞通知中需要明確調用ProceedingJoinPoint的proceed()方法來執(zhí)行被代理的方法.如果忘記這樣做就會導致通知被執(zhí)行了,但目標方法沒有被執(zhí)行。
- 注意: 環(huán)繞通知的方法需要返回目標方法執(zhí)行之后的結果,即調用 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 {
/**
* 申明切入點表達式,一般該方法內不需要添加其他方法
* 使用 @Pointcut 申明切入點表達式
* 后面的切入點直接使用方法名
*/
@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);
}
/**
* 后置通知(無論有無異常)
* 不能訪問目標方法的執(zhí)行結果
* @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);
}
/**
* 返回通知
* 在方法正常結束后執(zhí)行
* 可以訪問目標方法的執(zhí)行結果
* @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 類型的參數
* 類似于動態(tài)代理全過程
* 必須有返回值(目標方法返回值)
* @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í)行目標方法
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ōu)先級是不確定的.
- 切面的優(yōu)先級可以通過實現(xiàn) Ordered 接口或利用
@Order注解指定. - 實現(xiàn) Ordered 接口,
getOrder()方法的返回值越小, 優(yōu)先級越高. - 若使用
@Order注解,序號出現(xiàn)在注解中
4、重用切入點定義
- 在編寫 AspectJ 切面時,可以直接在通知注解中書寫切入點表達式,但同一個切點表達式可能會在多個通知中重復出現(xiàn)。
- 在 AspectJ 切面中, 可以通過 @Pointcut注解將一個切入點聲明成簡單的方法,切入點的方法體通常是空的,因為將切入點定義與應用程序邏輯混在一起是不合理的。
- 切入點方法的訪問控制符同時也控制著這個切入點的可見性,如果切入點要在多個切面中共用, 最好將它們集中在一個公共的類中,在這種情況下, 它們必須被聲明為 public。在引入這個切入點時,必須將類名也包括在內,如果類沒有與這個切面放在同一個包中, 還必須包含包名。
- 其他通知可以通過方法名稱引入該切入點。
到此這篇關于詳解Spring中的AOP及AspectJ五大通知注解的文章就介紹到這了,更多相關Spring的AOP及AspectJ內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Spring MVC+MyBatis+MySQL實現(xiàn)分頁功能實例
分頁功能是我們日常開發(fā)中經常會遇到的,下面這篇文章主要給大家介紹了Spring MVC+MyBatis+MySQL實現(xiàn)分頁功能的相關資料,文中介紹的非常詳細,對大家具有一定的參考學習價值,需要的朋友們下面來一起看看吧。2017-06-06
springboot如何開啟一個監(jiān)聽線程執(zhí)行任務
這篇文章主要介紹了springboot如何開啟一個監(jiān)聽線程執(zhí)行任務問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-02-02
Java基礎之多線程方法狀態(tài)和創(chuàng)建方法
Java中可以通過Thread類和Runnable接口來創(chuàng)建多個線程,下面這篇文章主要給大家介紹了關于Java基礎之多線程方法狀態(tài)和創(chuàng)建方法的相關資料,需要的朋友可以參考下2021-09-09

