Spring之@Aspect中通知的5種方式詳解
@Aspect中有5種通知
- @Before:前置通知, 在方法執(zhí)行之前執(zhí)行
- @Aroud:環(huán)繞通知, 圍繞著方法執(zhí)行
- @After:后置通知, 在方法執(zhí)行之后執(zhí)行
- @AfterReturning:返回通知, 在方法返回結果之后執(zhí)行
- @AfterThrowing:異常通知, 在方法拋出異常之后
這幾種通知用起來都比較簡單,都是通過注解的方式,將這些注解標注在@Aspect類的方法上,這些方法就會對目標方法進行攔截,下面我們一個個來看一下。
@Before:前置通知
介紹
定義一個前置通知
@Aspect
public class BeforeAspect {
@Before("execution(* com.javacode2018.aop.demo10.test1.Service1.*(..))")
public void before(JoinPoint joinPoint) {
System.out.println("我是前置通知!");
}
}- 類上需要使用
@Aspect標注 - 任意方法上使用
@Before標注,將這個方法作為前置通知,目標方法被調用之前,會自動回調這個方法 - 被
@Before標注的方法參數可以為空,或者為JoinPoint類型,當為JoinPoint類型時,必須為第一個參數 - 被
@Before標注的方法名稱可以隨意命名,符合java規(guī)范就可以,其他通知也類似
@Before中value的值為切入點表達式,也可以采用引用的方式指定切入點,如:
package com.javacode2018.aop.demo10.test1;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class BeforeAspect {
@Pointcut("execution(* com.javacode2018.aop.demo10.test1.Service1.*(..))")
public void pc() {
}
@Before("com.javacode2018.aop.demo10.test1.BeforeAspect.pc()")
public void before(JoinPoint joinPoint) {
System.out.println("我是前置通知!");
}
}此時,before方法上面的切入引用了pc方法上面的@Pointcut的值

案例
來個普通的service
package com.javacode2018.aop.demo10.test1;
public class Service1 {
public String say(String name) {
return "你好:" + name;
}
public String work(String name) {
return "開始工作了:" + name;
}
}給上面的類定義一個前置通知,Service1中的所有方法執(zhí)行執(zhí)行,輸出一段文字我是前置通知!
package com.javacode2018.aop.demo10.test1;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class BeforeAspect1 {
@Pointcut("execution(* com.javacode2018.aop.demo10.test1.Service1.*(..))")
public void pc() {
}
@Before("com.javacode2018.aop.demo10.test1.BeforeAspect1.pc()")
public void before(JoinPoint joinPoint) {
System.out.println("我是前置通知!");
}
}測試代碼
package com.javacode2018.aop.demo10;
import com.javacode2018.aop.demo10.test1.BeforeAspect1;
import com.javacode2018.aop.demo10.test1.Service1;
import org.junit.Test;
import org.springframework.aop.aspectj.annotation.AspectJProxyFactory;
public class AopTest10 {
@Test
public void test1() {
Service1 target = new Service1();
Class<BeforeAspect1> aspectClass = BeforeAspect1.class;
AspectJProxyFactory proxyFactory = new AspectJProxyFactory();
proxyFactory.setTarget(target);
proxyFactory.addAspect(aspectClass);
Service1 proxy = proxyFactory.getProxy();
System.out.println(proxy.say("路人"));
System.out.println(proxy.work("路人"));
}
}運行輸出
我是前置通知!
你好:路人
我是前置通知!
開始工作了:路人
對應的通知類
@Before通知最后會被解析為下面這個通知類
org.springframework.aop.aspectj.AspectJMethodBeforeAdvice
通知中獲取被調方法信息
通知中如果想獲取被調用方法的信息,分2種情況
- 非環(huán)繞通知,可以將
org.aspectj.lang.JoinPoint作為通知方法的第1個參數,通過這個參數獲取被調用方法的信息 - 如果是環(huán)繞通知,可以將
org.aspectj.lang.ProceedingJoinPoint作為方法的第1個參數,通過這個參數獲取被調用方法的信息
JoinPoint:連接點信息
org.aspectj.lang.JoinPoint
提供訪問當前被通知方法的目標對象、代理對象、方法參數等數據:
package org.aspectj.lang;
import org.aspectj.lang.reflect.SourceLocation;
public interface JoinPoint {
String toString(); //連接點所在位置的相關信息
String toShortString(); //連接點所在位置的簡短相關信息
String toLongString(); //連接點所在位置的全部相關信息
Object getThis(); //返回AOP代理對象
Object getTarget(); //返回目標對象
Object[] getArgs(); //返回被通知方法參數列表,也就是目前調用目標方法傳入的參數
Signature getSignature(); //返回當前連接點簽名,這個可以用來獲取目標方法的詳細信息,如方法Method對象等
SourceLocation getSourceLocation();//返回連接點方法所在類文件中的位置
String getKind(); //連接點類型
StaticPart getStaticPart(); //返回連接點靜態(tài)部分
} ProceedingJoinPoint:環(huán)繞通知連接點信息
用于環(huán)繞通知,內部主要關注2個方法,一個有參的,一個無參的,用來繼續(xù)執(zhí)行攔截器鏈上的下一個通知。
package org.aspectj.lang;
import org.aspectj.runtime.internal.AroundClosure;
public interface ProceedingJoinPoint extends JoinPoint {
/**
* 繼續(xù)執(zhí)行下一個通知或者目標方法的調用
*/
public Object proceed() throws Throwable;
/**
* 繼續(xù)執(zhí)行下一個通知或者目標方法的調用
*/
public Object proceed(Object[] args) throws Throwable;
}Signature:連接點簽名信息
注意JoinPoint#getSignature()這個方法,用來獲取連接點的簽名信息,這個比較重要
Signature getSignature();
通常情況,spring中的aop都是用來對方法進行攔截,所以通常情況下連接點都是一個具體的方法,Signature有個子接口
org.aspectj.lang.reflect.MethodSignature
JoinPoint#getSignature()都可以轉換轉換為MethodSignature類型,然后可以通過這個接口提供的一些方法來獲取被調用的方法的詳細信息。
下面對上面的前置通知的案例改造一下,獲取被調用方法的詳細信息,新建一個Aspect類:BeforeAspect2
package com.javacode2018.aop.demo10.test2;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import java.lang.reflect.Method;
@Aspect
public class BeforeAspect2 {
@Pointcut("execution(* com.javacode2018.aop.demo10.test1.Service1.*(..))")
public void pc() {
}
@Before("com.javacode2018.aop.demo10.test2.BeforeAspect2.pc()")
public void before(JoinPoint joinPoint) {
//獲取連接點簽名
Signature signature = joinPoint.getSignature();
//將其轉換為方法簽名
MethodSignature methodSignature = (MethodSignature) signature;
//通過方法簽名獲取被調用的目標方法
Method method = methodSignature.getMethod();
//輸出方法信息
System.out.println(method);
}
}測試用例
@Test
public void test2() {
Service1 target = new Service1();
Class<BeforeAspect2> aspectClass = BeforeAspect2.class;
AspectJProxyFactory proxyFactory = new AspectJProxyFactory();
proxyFactory.setTarget(target);
proxyFactory.addAspect(aspectClass);
Service1 proxy = proxyFactory.getProxy();
System.out.println(proxy.say("路人"));
System.out.println(proxy.work("路人"));
}運行輸出
public java.lang.String com.javacode2018.aop.demo10.test1.Service1.say(java.lang.String)
你好:路人
public java.lang.String com.javacode2018.aop.demo10.test1.Service1.work(java.lang.String)
開始工作了:路人
@Around:環(huán)繞通知
介紹
環(huán)繞通知會包裹目標目標方法的執(zhí)行,可以在通知內部調用ProceedingJoinPoint.process方法繼續(xù)執(zhí)行下一個攔截器。
用起來和@Before類似,但是有2點不一樣
若需要獲取目標方法的信息,需要將ProceedingJoinPoint作為第一個參數
通常使用Object類型作為方法的返回值,返回值也可以為void
特點
環(huán)繞通知比較特殊,其他4種類型的通知都可以用環(huán)繞通知來實現(xiàn)。
案例
通過環(huán)繞通知來統(tǒng)計方法的耗時。
package com.javacode2018.aop.demo10.test3;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import java.lang.reflect.Method;
@Aspect
public class AroundAspect3 {
@Pointcut("execution(* com.javacode2018.aop.demo10.test1.Service1.*(..))")
public void pc() {
}
@Around("com.javacode2018.aop.demo10.test3.AroundAspect3.pc()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
//獲取連接點簽名
Signature signature = joinPoint.getSignature();
//將其轉換為方法簽名
MethodSignature methodSignature = (MethodSignature) signature;
//通過方法簽名獲取被調用的目標方法
Method method = methodSignature.getMethod();
long startTime = System.nanoTime();
//調用proceed方法,繼續(xù)調用下一個通知
Object returnVal = joinPoint.proceed();
long endTime = System.nanoTime();
long costTime = endTime - startTime;
//輸出方法信息
System.out.println(String.format("%s,耗時(納秒):%s", method.toString(), costTime));
//返回方法的返回值
return returnVal;
}
}測試用例
@Test
public void test3() {
Service1 target = new Service1();
Class<AroundAspect3> aspectClass = AroundAspect3.class;
AspectJProxyFactory proxyFactory = new AspectJProxyFactory();
proxyFactory.setTarget(target);
proxyFactory.addAspect(aspectClass);
Service1 proxy = proxyFactory.getProxy();
System.out.println(proxy.say("路人"));
System.out.println(proxy.work("路人"));
}運行輸出
public java.lang.String com.javacode2018.aop.demo10.test1.Service1.say(java.lang.String),耗時(納秒):19000500
你好:路人
public java.lang.String com.javacode2018.aop.demo10.test1.Service1.work(java.lang.String),耗時(納秒):59600
開始工作了:路人
對應的通知類
@Around通知最后會被解析為下面這個通知類
org.springframework.aop.aspectj.AspectJAroundAdvice
@After:后置通知
介紹
后置通知,在方法執(zhí)行之后執(zhí)行,用法和前置通知類似。
特點
- 不管目標方法是否有異常,后置通知都會執(zhí)行
- 這種通知無法獲取方法返回值
- 可以使用
JoinPoint作為方法的第一個參數,用來獲取連接點的信息
案例
在Service1中任意方法執(zhí)行完畢之后,輸出一行日志。
package com.javacode2018.aop.demo10.test4;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
@Aspect
public class AfterAspect4 {
@Pointcut("execution(* com.javacode2018.aop.demo10.test1.Service1.*(..))")
public void pc() {
}
@After("com.javacode2018.aop.demo10.test4.AfterAspect4.pc()")
public void after(JoinPoint joinPoint) throws Throwable {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
System.out.println(String.format("%s,執(zhí)行完畢!", methodSignature.getMethod()));
}
}測試案例
@Test
public void test4() {
Service1 target = new Service1();
Class<AfterAspect4> aspectClass = AfterAspect4.class;
AspectJProxyFactory proxyFactory = new AspectJProxyFactory();
proxyFactory.setTarget(target);
proxyFactory.addAspect(aspectClass);
Service1 proxy = proxyFactory.getProxy();
System.out.println(proxy.say("路人"));
System.out.println(proxy.work("路人"));
}運行輸出
public java.lang.String com.javacode2018.aop.demo10.test1.Service1.say(java.lang.String),執(zhí)行完畢!
你好:路人
public java.lang.String com.javacode2018.aop.demo10.test1.Service1.work(java.lang.String),執(zhí)行完畢!
開始工作了:路人
對應的通知類
@After通知最后會被解析為下面這個通知類
org.springframework.aop.aspectj.AspectJAfterAdvice
這個類中有invoke方法,這個方法內部會調用被通知的方法,其內部采用try..finally的方式實現(xiàn)的,所以不管目標方法是否有異常,通知一定會被執(zhí)行。
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
try {
//繼續(xù)執(zhí)行下一個攔截器
return mi.proceed();
}
finally {
//內部通過反射調用被@After標注的方法
invokeAdviceMethod(getJoinPointMatch(), null, null);
}
}@AfterReturning:返回通知
用法
返回通知,在方法返回結果之后執(zhí)行。
特點
- 可以獲取到方法的返回值
- 當目標方法返回異常的時候,這個通知不會被調用,這點和@After通知是有區(qū)別的
案例
后置通知中打印出方法及返回值信息。
package com.javacode2018.aop.demo10.test5;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
@Aspect
public class AfterReturningAspect5 {
@Pointcut("execution(* com.javacode2018.aop.demo10.test1.Service1.*(..))")
public void pc() {
}
@AfterReturning(value = "com.javacode2018.aop.demo10.test5.AfterReturningAspect5.pc()", returning = "retVal")
public void afterReturning(JoinPoint joinPoint, Object retVal) throws Throwable {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
System.out.println(String.format("%s返回值:%s", methodSignature.getMethod(), retVal));
}
}注意@AfterReturning注解,用到了2個參數
- value:用來指定切入點
- returning:用來指定返回值對應方法的參數名稱,返回值對應方法的第二個參數,名稱為retVal
對應的通知類
@AfterReturning通知最后會被解析為下面這個通知類
org.springframework.aop.aspectj.AspectJAfterReturningAdvice
@AfterThrowing:異常通知
用法
在方法拋出異常之后會回調@AfterThrowing標注的方法。
@AfterThrowing標注的方法可以指定異常的類型,當被調用的方法觸發(fā)該異常及其子類型的異常之后,會觸發(fā)異常方法的回調。也可以不指定異常類型,此時會匹配所有異常。
未指定異常類型
未指定異常類型,可以匹配所有異常類型,如下
@AfterThrowing(value = "切入點") public void afterThrowing()
指定異常類型
通過@AfterThrowing的throwing指定參數異常參數名稱,我們用方法的第二個參數用來接收異常,第二個參數名稱為e,下面的代碼,當目標方法發(fā)生IllegalArgumentException異常及其子類型異常時,下面的方法會被回調。
@AfterThrowing(value = "com.javacode2018.aop.demo10.test6.AfterThrowingAspect6.pc()", throwing = "e") public void afterThrowing(JoinPoint joinPoint, IllegalArgumentException e)
特點
不論異常是否被異常通知捕獲,異常還會繼續(xù)向外拋出。
案例
Service1中加了login方法,用戶名不是路人甲java時拋出異常。
package com.javacode2018.aop.demo10.test1;
public class Service1 {
public String say(String name) {
return "你好:" + name;
}
public String work(String name) {
return "開始工作了:" + name;
}
public boolean login(String name) {
if (!"路人甲java".equals(name)) {
throw new IllegalArgumentException("非法訪問!");
}
return true;
}
}來個異常通知
package com.javacode2018.aop.demo10.test6;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
@Aspect
public class AfterThrowingAspect6 {
@Pointcut("execution(* com.javacode2018.aop.demo10.test1.Service1.*(..))")
public void pc() {
}
@AfterThrowing(value = "com.javacode2018.aop.demo10.test6.AfterThrowingAspect6.pc()", throwing = "e")
public void afterThrowing(JoinPoint joinPoint, IllegalArgumentException e) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
System.out.println(String.format("%s發(fā)生異常,異常信息:%s", methodSignature.getMethod(), e.getMessage()));
}
}測試用例
@Test
public void test6() {
Service1 target = new Service1();
Class<AfterThrowingAspect6> aspectClass = AfterThrowingAspect6.class;
AspectJProxyFactory proxyFactory = new AspectJProxyFactory();
proxyFactory.setTarget(target);
proxyFactory.addAspect(aspectClass);
Service1 proxy = proxyFactory.getProxy();
proxy.login("路人");
}運行輸出
public boolean com.javacode2018.aop.demo10.test1.Service1.login(java.lang.String)發(fā)生異常,異常信息:非法訪問!
java.lang.IllegalArgumentException: 非法訪問!
at com.javacode2018.aop.demo10.test1.Service1.login(Service1.java:14)
at com.javacode2018.aop.demo10.test1.Service1$$FastClassBySpringCGLIB$$ea03ccbe.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:769)
對應的通知類
@AfterThrowing通知最后會被解析為下面這個通知類
org.springframework.aop.aspectj.AspectJAfterThrowingAdvice
來看一下這個類的invoke方法,這個方法是關鍵
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
try {
//繼續(xù)調用下一個攔截器鏈
return mi.proceed();
}
catch (Throwable ex) {
//判斷ex和需要不糊的異常是否匹配
if (shouldInvokeOnThrowing(ex)) {
//通過反射調用@AfterThrowing標注的方法
invokeAdviceMethod(getJoinPointMatch(), null, ex);
}
//繼續(xù)向外拋出異常
throw ex;
}
}幾種通知對比
| 通知類型 | 執(zhí)行時間點 | 可獲取返回值 | 目標方法異常時是否會執(zhí)行 |
|---|---|---|---|
| @Before | 方法執(zhí)行之前 | 否 | 是 |
| @Around | 環(huán)繞方法執(zhí)行 | 是 | 自己控制 |
| @After | 方法執(zhí)行后 | 否 | 是 |
| @AfterReturning | 方法執(zhí)行后 | 是 | 否 |
| @AfterThrowing | 方法發(fā)生異常后 | 否 | 是 |

到此這篇關于Spring之@Aspect中通知的5種方式詳解的文章就介紹到這了,更多相關Spring @Aspect通知內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
SprinBoot如何集成參數校驗Validator及參數校驗的高階技巧
這篇文章主要介紹了SprinBoot如何集成參數校驗Validator及參數校驗的高階技巧包括自定義校驗、分組校驗,本文分步驟給大家介紹的非常詳細,需要的朋友可以參考下2022-05-05
springboot整合JavaCV實現(xiàn)視頻截取第N幀并保存圖片
這篇文章主要為大家詳細介紹了springboot如何整合JavaCV實現(xiàn)視頻截取第N幀并保存為圖片,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起了解一下2023-08-08
詳細聊聊SpringBoot中動態(tài)切換數據源的方法
在大型分布式項目中,經常會出現(xiàn)多數據源的情況,下面這篇文章主要給大家介紹了關于SpringBoot中動態(tài)切換數據源的相關資料,文中通過示例代碼介紹的非常詳細,需要的朋友可以參考下2021-09-09
Hibernate環(huán)境搭建與配置方法(Hello world配置文件版)
這篇文章主要介紹了Hibernate環(huán)境搭建與配置方法,這里演示Hello world配置文件版的具體實現(xiàn)步驟與相關代碼,需要的朋友可以參考下2016-03-03
使用Springboot實現(xiàn)word在線編輯保存
PageOffice目前支持的Web編程語言及架構有:Java(JSP、SSH、MVC等),ASP.NET(C#、VB.NET、MVC、Razor等),PHP,ASP,本篇文章就帶你使用Springboot整合PageOffice實現(xiàn)word在線編輯保存2021-08-08

