目錄
- 一、AOP 與 Spring AOP
- 二、Spring AOP簡單實現
- 三、詳解Spring AOP
- 3.1 Spring AOP 核心概念
- 3.1.1 切點(Pointcut)
- 3.1.2 連接點(Join Point)
- 3.1.3 通知(Advice)
- 3.1.4 切面(Aspect)
- 3.2 通知類型
- 3.3 公共切點引用@PointCut
- 3.4 切點優先級@Order
- 3.5 切點表達式
- 3.5.1 execution
- 3.5.2 @annotation

一、AOP 與 Spring AOP
AOP:Aspect Oriented Programming(?向方?編程)。是一種對某一類事情集中處理的思想。
Spring AOP:就是對AOP思想的一種實現。
二、Spring AOP簡單實現
我們簡單實現一個統計每個接口的用時。
引入依賴:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency>
寫AOP實現:
- 類使用注解@Aspect修飾
- 方法參數為ProceedingJoinPoint 類,代表要實現的方法(只能在Around通知下寫)
- 方法使用注解@Around,參數是對應的路徑的切點
- ProceedingJoinPoint 的參數執行proceed方法。
package com.example.library.aspect;import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;@Aspect
@Component
@Slf4j
public class TimeAspect {@Around("execution(* com.example.library.controller.*.*(..) )")public Object recordTime(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {//開始時間long start = System.currentTimeMillis();//執行方法Object result = proceedingJoinPoint.proceed();//結束時間long end = System.currentTimeMillis();log.info("執行時間:"+ (end-start) + "ms");return result;}
}
三、詳解Spring AOP
3.1 Spring AOP 核心概念
Spring AOP 核心概念:切點,連接點,通知,切面。
我們以上面的代碼來介紹。
3.1.1 切點(Pointcut)
切點:就是告訴程序哪些方法需要使用到接下來的功能。
上面的@Around注解的參數就是切點表達式。
3.1.2 連接點(Join Point)
連接點:滿?切點表達式規則的?法,就是連接點。也就是可以AOP控制的?法。
就像上面的代碼的連接點就是:com.example.library.controller
路徑下的所有方法。
切點和連接點的關系:
- 連接點是滿?切點表達式的元素。
- 切點可以看做是保存了眾多連接點的?個集合。
3.1.3 通知(Advice)
通知:這個Spring AOP方法要實現的功能就是通知。
就像上面的實現一個統計每個接口的用時的需求,就是通知。
3.1.4 切面(Aspect)
切?(Aspect) = 切點(Pointcut) + 通知(Advice)。
通過切?就能夠描述當前AOP程序需要針對于哪些?法,在什么時候執?什么樣的操作。
切?既包含了通知邏輯的定義,也包括了連接點的定義。
3.2 通知類型
Spring中AOP的通知類型有以下?種:
- @Around:環繞通知,此注解標注的通知?法在?標?法前,后都被執?。
- @Before:前置通知,此注解標注的通知?法在?標?法前被執?。
- @After:后置通知,此注解標注的通知?法在?標?法后被執?,?論是否有異常都會執?。
- @AfterReturning:返回后通知,此注解標注的通知?法在?標?法后被執?,有異常不會執?。
- @AfterThrowing:異常后通知,此注解標注的通知?法發?異常后執?。
效果:
package com.example.demoaop;import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;@Component
@Slf4j
@Aspect
public class TestAspect {//前置通知@Before("execution(* com.example.demoaop.*.*(..) )")public void testBefore() {log.info("Before 方法執行前");}//后置通知@After("execution(* com.example.demoaop.*.*(..) )")public void testAfter() {log.info("After 方法執行后");}//返回后通知@AfterReturning("execution(* com.example.demoaop.*.*(..) )")public void testAfterReturning() {log.info("AfterReturning 返回后通知");}//拋出異常后通知@AfterThrowing("execution(* com.example.demoaop.*.*(..) )")public void testAfterThrowing() {log.info("AfterThrowing 拋出異常后通知");}//環繞通知@Around("execution(* com.example.demoaop.*.*(..) )")public void testAround(ProceedingJoinPoint pjp) throws Throwable {log.info("Around 方法執行前");Object proceed = pjp.proceed();log.info("Around 方法執行后");}
}
3.3 公共切點引用@PointCut
當我們的切點表達式是一樣的時候,像上面我們還是在每一個通知類型的注解中,都使用了相同的表達式。
我們就可以使用方法注解@PointCut將切點表達式提取出來,然后后面使用只需要寫方法名即可。
在其他切點類中也可以調用,需要將@PointCut注解所在類的路徑寫出來。
package com.example.demoaop;import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;@Component
@Slf4j
@Aspect
public class TestAspect {@Pointcut("execution(* com.example.demoaop.*.*(..) )")public void pc(){}//前置通知@Before("pc()")public void testBefore() {log.info("TestAspect Before 方法執行前");}//后置通知@After("pc()")public void testAfter() {log.info("TestAspect After 方法執行后");}
}
package com.example.demoaop;import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;@Component
@Slf4j
@Aspect
public class TestAspect2 {//前置通知@Before("com.example.demoaop.TestAspect.pc()")public void testBefore() {log.info("TestAspect2 Before 方法執行前");}//后置通知@After("pc()")public void testAfter() {log.info("TestAspect2 After 方法執行后");}}
結果:
可以看見生效了,而且在其他切點類中只有加上了路徑的才生效了。
3.4 切點優先級@Order
我們定義3個一樣的切點類,看他們的輸出順序:
存在多個切?類時,默認按照切?類的類名字?排序:
- @Before 通知:字?排名靠前的先執?
- @After 通知:字?排名靠前的后執?
但這種?式不?便管理,我們的類名更多還是具備?定含義的。
Spring 給我們提供了?個新的注解,來控制這些切?通知的執?順序:@Order
我們將切點類的優先級換一下:
@Component
@Slf4j
@Aspect
@Order(1)
public class TestAspect3 {//前置通知@Before("com.example.demoaop.TestAspect.pc()")public void testBefore() {log.info("TestAspect3 Before 方法執行前");}//后置通知@After("com.example.demoaop.TestAspect.pc()")public void testAfter() {log.info("TestAspect3 After 方法執行后");}}
@Component
@Slf4j
@Aspect
@Order(2)
public class TestAspect2 {//前置通知@Before("com.example.demoaop.TestAspect.pc()")public void testBefore() {log.info("TestAspect2 Before 方法執行前");}//后置通知@After("com.example.demoaop.TestAspect.pc()")public void testAfter() {log.info("TestAspect2 After 方法執行后");}}
@Component
@Slf4j
@Aspect
@Order(3)
public class TestAspect {@Pointcut("execution(* com.example.demoaop.*.*(..) )")public void pc(){}//前置通知@Before("pc()")public void testBefore() {log.info("TestAspect Before 方法執行前");}//后置通知@After("pc()")public void testAfter() {log.info("TestAspect After 方法執行后");}
}
執行結果:
規律:
@Order 注解標識的切?類,執?順序如下:
- @Before 通知:數字越?先執?
- @After 通知:數字越?先執?
像下圖的表示,箭頭代表執行過程:
3.5 切點表達式
切點表達式用來描述切點,常有以下兩種類型的切點表達式:execution 和 @annotation
3.5.1 execution
語法:
execution(<訪問修飾限定符> <返回類型> <包名.類名.方法名(方法參數)> <異常>)
含義:
- 訪問修飾限定符:表示切點對應的方法的訪問修飾限定符
- 返回類型:表示切點對應的方法的返回類型
- 包名.類名.方法名(方法參數):表示切點對應的方法的路徑及參數
- 異常:表示切點對應的方法拋出的異常
- 訪問修飾限定符 和 異常 可以省略
切點表達式?持通配符表達:
- :* 匹配任意字符,只匹配?個元素(返回類型,包,類名,?法或者?法參數)
1.1. 包名使? * 表?任意包(?層包使??個 * )
1.2. 類名使? * 表?任意類
1.3. 返回值使? * 表?任意返回值類型
1.4. ?法名使? * 表?任意?法
1.5. 參數使? * 表??個任意類型的參數 - : 兩個點 . . 匹配多個連續的任意符號,可以通配任意層級的包,或任意類型,任意個數的參數
2.1. 使? . . 配置包名,標識此包以及此包下的所有?包
2.2. 可以使? . . 配置參數,任意個任意類型的參數
例子:
- TestController 下的 public修飾,返回類型為String ?法名為t1的?參?法
execution(public String com.example.demo.TestController.t1())
- 匹配 TestController 下的所有?參?法
execution(* com.example.demo.TestController.*())
- 匹配controller包下所有的類的所有?法
execution(* com.example.demo.controller.*.*(..))
3.5.2 @annotation
當我們要落實到不同類下個幾個方法,用上面的execution就有點捉襟見肘。
我們就可以使用?定義注解的?式以及另?種切點表達式 @annotation 來描述這?類的切點。
自定義注解:
- 在自定義類的時候選擇annotation:
- 然后就跟我們前面使用的注解一樣包含,生命周期@Retention,作用范圍@Target,交給Spring管理。
@Component
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAspect {
}
定義切面類:
- 使用@Aspect注解修飾,
- 交給Spring管理
- 在通知類型的注解中使用:@annotation(自定義注解路徑) 作為參數。
@Slf4j
@Component
@Aspect
public class MyAspectDemo {@Around("@annotation( com.example.demoaop.MyAspect)")public void around(ProceedingJoinPoint pjp) throws Throwable {log.info("annotation 運行前");pjp.proceed();log.info("annotation 運行后");}
}
通過上面的方法,使用了自定義注解修飾的方法,就可以添加切面類的通知。
@RequestMapping("/test")
@RestController
@Slf4j
public class Test {@RequestMapping("/f1")public String f1() {log.info("f1");return "s1";}@MyAspect@RequestMapping("/f2")public Integer f2() {log.info("f2");return 1;}@RequestMapping("/f3")public Boolean f3() {log.info("f3");return false;}
}
訪問f2 f1 f3 的結果:
除了上面講的基于注解的方式實現Spring AOP 還有遠古的通過xml和代理的方式實現。參考Spring AOP其它實現方式