首頁 > 軟體

基於spring AOP @Around @Before @After的區別說明

2021-02-19 15:22:57

此段小程式碼演示了spring aop中@Around @Before @After三個註解的區別

@Before是在所攔截方法執行之前執行一段邏輯。

@After 是在所攔截方法執行之後執行一段邏輯。

@Around是可以同時在所攔截方法的前後執行一段邏輯。

連線點(JoinPoint) 這個就更好解釋了,就是spring允許你是通知(Advice)的地方,那可就真多了,基本每個方法的前、後(兩者都有也行),或丟擲異常是時都可以是連線點,spring只支援方法連線點。

其他如AspectJ還可以讓你在構造器或屬性注入時都行,不過那不是咱們關注的,只要記住,和方法有關的前前後後都是連線點。

package com.itsoft.action;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.stereotype.Controller;
/**
 * 
 * @author zxf
 * 演示aop測試類
 */
@Controller
public class UserAction {
 public void queryUsers(){
  System.out.println("查詢所有使用者【all users list】");
 }
 public static void main(String[] args) {
  ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("application-aop.xml");
  UserAction userAction = (UserAction)ctx.getBean("userAction");
  userAction.queryUsers();
  ctx.destroy();
 }
}
package com.itsoft;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
 * 
 * @author Administrator
 * 通過aop攔截後執行具體操作
 */
@Aspect
@Component
public class LogIntercept {
 @Pointcut("execution(public * com.itsoft.action..*.*(..))")
 public void recordLog(){}
 @Before("recordLog()")
 public void before() {
  this.printLog("已經記錄下操作紀錄檔@Before 方法執行前");
 }
 @Around("recordLog()")
 public void around(ProceedingJoinPoint pjp) throws Throwable{
  this.printLog("已經記錄下操作紀錄檔@Around 方法執行前");
  pjp.proceed();
  this.printLog("已經記錄下操作紀錄檔@Around 方法執行後");
 }
 @After("recordLog()")
 public void after() {
  this.printLog("已經記錄下操作紀錄檔@After 方法執行後");
 }
 private void printLog(String str){
  System.out.println(str);
 }
}
<?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"
  xmlns:context="http://www.springframework.org/schema/context"
  xsi:schemaLocation="
  http://www.springframework.org/schema/context
  http://www.springframework.org/schema/context/spring-context-3.0.xsd
  http://www.springframework.org/schema/beans 
  http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
  http://www.springframework.org/schema/aop 
  http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
 <context:annotation-config />
 <context:component-scan base-package="com.itsoft"/>
 <aop:aspectj-autoproxy />
</beans>

補充:spring aop的@Before,@Around,@After,@AfterReturn,@AfterThrowing的理解

1.AOP的基本概念

切面(Aspect) :通知(advice)和切入點(pointcut)共同組成了切面(aspect),時間、地點和要發生的「故事」。

可以從註解方式來理解,程式碼如下。

@aspect為類上面的註解——切面

@pointcut(…)——切入點。為此類內一個空方法上面的註解。可以把攔截的地址表示式表示為方法簽名,利於使用起來方便。

@before@after等——通知。為此類下面的方法上面的註解。

三者在一塊組成一個切面。

@Aspect
public class ExampleAspect {
 @Pointcut("execution(* com.psjay.example.spring.aop.*.*(..))")
 public void aPointcut() {
 }
 @Before("aPointcut()")
 public void beforeAdvice() {
  System.out.println("before advice is executed!");
 }
}

連線點(Joinpoint) :程式能夠應用通知的一個「時機」,這些「時機」就是連線點,例如方法被呼叫時、異常被丟擲時等等。——可以理解為被aop攔截的類或者方法就是連線點。

通知(Advice) :通知定義了切面是什麼以及何時使用。描述了切面要完成的工作和何時需要執行這個工作。——可以理解為被註解有@Before等advice註解的安全校驗的方法,攔截了過來的請求要做什麼邏輯的校驗。

切入點(Pointcut) :通知定義了切面要發生的「故事」和時間,那麼切入點就定義了「故事」發生的地點,例如某個類或方法的名稱。——可以理解為切面切向哪裡?是個類或者某層的包路徑。

目標物件(Target Object) :即被通知的物件。

AOP代理(AOP Proxy) 在Spring AOP中有兩種代理方式,JDK動態代理和CGLIB代理。預設情況下,TargetObject實現了介面時,則採用JDK動態代理;反之,採用CGLIB代理。

織入(Weaving)把切面應用到目標物件來建立新的代理物件的過程,織入一般發生在如下幾個時機:

(1)編譯時:當一個類檔案被編譯時進行織入,這需要特殊的編譯器才能做到,例如AspectJ的織入編譯器;

(2)類載入時:使用特殊的ClassLoader在目標類被載入到程式之前增強類的位元組程式碼;

(3)執行時:切面在執行的某個時刻被織入,SpringAOP就是以這種方式織入切面的,原理是使用了JDK的動態代理。

2 通知(Advice)型別的說明

@Before 前置通知(Before advice) :在某連線點(JoinPoint)——核心程式碼(類或者方法)之前執行的通知,但這個通知不能阻止連線點前的執行。

為啥不能阻止執行緒進入核心程式碼呢?

因為@Before註解的方法入參不能傳ProceedingJoinPoint,而只能傳入JoinPoint。

要知道從aop走到核心程式碼就是通過呼叫ProceedingJionPoint的proceed()方法。

而JoinPoint沒有這個方法。

這裡牽扯區別這兩個類:Proceedingjoinpoint 繼承了 JoinPoint 。

是在JoinPoint的基礎上暴露出 proceed 這個方法。proceed很重要,這個是aop代理鏈執行的方法。

暴露出這個方法,就能支援 aop:around 這種切面(而其他的幾種切面只需要用到JoinPoint,這跟切面型別有關), 能決定是否走代理鏈還是走自己攔截的其他邏輯。

建議看一下 JdkDynamicAopProxy的invoke方法,瞭解一下代理鏈的執行原理。

這樣你就能明白 proceed方法的重要性。

@After 後通知(After advice) :當某連線點退出的時候執行的通知(不論是正常返回還是異常退出)。

@AfterReturning 返回後通知(After return advice) :在某連線點正常完成後執行的通知,不包括丟擲異常的情況。

@Around 環繞通知(Around advice) :包圍一個連線點的通知,類似Web中Servlet規範中的Filter的doFilter方法。

可以在方法的呼叫前後完成自定義的行為,也可以選擇不執行。

這時aop的最重要的,最常用的註解。

用這個註解的方法入參傳的是ProceedingJionPoint pjp,可以決定當前執行緒能否進入核心方法中——通過呼叫pjp.proceed();

@AfterThrowing 丟擲異常後通知(After throwing advice) : 在方法丟擲異常退出時執行的通知。

3 advice(通知)註解的執行先後順序

這裡說下簡單情況——針對一個方法只被一個aspect類攔截時,aspect類內部的 advice 將按照以下的順序進行執行情況如下:

解釋:執行到核心業務方法或者類時,會先執行AOP。在aop的邏輯內,先走@Around註解的方法。

然後是@Before註解的方法,然後這兩個都通過了,走核心程式碼,核心程式碼走完,無論核心有沒有返回值,都會走@After方法。

然後如果程式無異常,正常返回就走@AfterReturn,有異常就走@AfterThrowing。

複雜的同一個方法被多個Aspect類攔截請參看博文:Spring AOP @Before @Around @After 等 advice 的執行順序。

4 在aop中校驗不通過如何不讓程式進入核心程式碼?

通過aop中註解的執行的先後順序我們知道,校驗發生在核心程式碼前面的只剩下兩個——@Before,@Around。

@Before : 這個註解只有在異常時才不會走核心方法——連線點。正常@Before無法阻止當前執行緒進入連線點。

@Around : 這個註解在連線點前後執行。並且註解的方法傳入的ProceedingJionPoint 類中封裝的代理方法proceed()可以讓當前執行緒從aop方法轉到連線點——核心程式碼方法。

所以一般我們用這個註解,如果aop的安全校驗不通過,則不呼叫proceed()方法,就永遠不會進入連線點。

除此外,要注意除了Around註解的方法可以傳ProceedingJionPoint 外,別的幾個都不能傳這個類。

但是普通的資料型別是不限制的。

註解的方法的返回值也不限制,可以自由限制。

以上為個人經驗,希望能給大家一個參考,也希望大家多多支援it145.com。如有錯誤或未考慮完全的地方,望不吝賜教。


IT145.com E-mail:sddin#qq.com