前言
Spring AOP(面向切面编程)是 Spring 框架提供的一个功能,通过预编译和运行期动态代理方式实现,用于在程序运行时动态地将横切关注点(cross-cutting concerns)与核心业务逻辑进行分离。横切关注点是那些散布在应用程序中的、与业务逻辑无关的功能,如日志记录、事务管理、安全性、性能统计等
AOP关键点
切面(Aspect):切面是一个模块化单元,它包含横切关注点和与之相关的通知(Advice)。通知定义了在什么时候、在哪个切入点(Join Point)上执行横切逻辑。
连接点(Join Point):连接点是在应用程序执行过程中能够被切面通知的点,如方法执行时、异常抛出时等。Spring AOP 仅支持方法执行连接点。
通知(Advice):通知是切面的一个方法,在连接点上执行特定的横切逻辑。Spring AOP 支持的通知类型包括前置通知(@Before)、后置通知(@AfterReturning)、异常通知(@AfterThrowing)、最终通知(@After)以及环绕通知(@Around)。
切点(Pointcut):切点是用于匹配连接点的表达式。它定义了哪些连接点会被执行通知。
切入点表达示
ChatGPT
AOP 切入点表达式语法用于定义切面中的切点,它确定了哪些连接点会被切面的通知所影响。Spring AOP 使用 AspectJ 的切入点表达式语法,支持一系列的语法元素来匹配连接点。以下是 AOP 切入点表达式的基本语法:
-
方法匹配符号:
-
execution
: 匹配方法执行连接点。 -
within
: 匹配指定类型内的方法执行连接点。 -
this
: 匹配当前代理对象类型的执行连接点。 -
target
: 匹配目标对象类型的执行连接点。 -
args
: 匹配方法参数类型的执行连接点。
-
-
通配符:
-
*
: 匹配任意数量字符。 -
..
: 匹配任意数量字符或子包。
-
-
运算符:
-
&&
: 逻辑与。 -
||
: 逻辑或。 -
!
: 逻辑非。
-
-
类型模式:
-
+
: 子类型。 -
..
: 任意数量子包。
-
-
方法模式:
-
*
: 匹配任意名称的方法。 -
()
:方法参数。 -
..
: 任意数量参数。
-
-
参数模式:
-
*
: 匹配任意类型的参数。 -
..
: 任意数量参数。
-
-
返回类型:
-
void
,int
,String
等:匹配特定的返回类型。
-
示例:
-
execution(* com.example.service.*.*(..))
:匹配com.example.service
包中所有类的所有方法。 -
execution(public * *(..))
:匹配所有公共方法。 -
execution(* set*(..))
:匹配所有以set
开头的方法。 -
execution(* com.example.service..*(..))
:匹配com.example.service
包及其子包中的所有方法。 -
within(com.example.service.*)
:匹配com.example.service
包中的所有方法。 -
args(String)
:匹配一个 String 类型参数的方法。 -
target(com.example.service.MyService)
:匹配所有目标对象为MyService
类型的方法。
这些是 AOP 切入点表达式的基本语法元素,你可以根据需要组合它们来定义复杂的切入点。
格式如图:
项目测试
准备springboot启动类
package com.luojie;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Applications {
public static void main(String[] args) {
SpringApplication.run(Applications.class, args);
}
}
准备测试controller类
package com.luojie.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class AOPTestController {
@GetMapping("/aop/test")
public Object testAOP(@RequestParam("ab") int a, @RequestParam("sr") String b) {
int i = b.compareTo(String.valueOf(a));
return i > 0 ? b : a;
}
}
准备切面
package com.luojie.config;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@Aspect // 表示该类是一个切面类
@Component // 将该类注册为一个bean放入IOC容器
@Slf4j
public class LogAspect {
/**
* 通知类型
* 前置通知:@Before()
* 返回通知:@AfterReturning
* 异常通知:@AfterThrowing
* 后置通知:@After()
* 环绕通知:@Around()
*/
@Pointcut(value = "execution(* com.luojie.controller.*.*(..))")
public void pointcut() {
}
// @Before(value = "切入点表达式") 也可以直接复用@Before("pointcut()")
@Before(value = "execution(* com.luojie.controller.*.*(..))")
public void before(JoinPoint joinPoint) {
String name = joinPoint.getSignature().getName(); // 获取方法名
Object[] args = joinPoint.getArgs();// 获取参数列表
log.info("前置通知: 开启调用,方法名:{}, 参数:{}", name, Arrays.toString(args));
}
@After(value = "pointcut()")
public void after(JoinPoint joinPoint) {
String name = joinPoint.getSignature().getName(); // 获取方法名
log.info("后置通知: 开启调用,方法名:{}", name);
}
@AfterReturning(value = "pointcut()", returning = "result")
public void AfterReturning(JoinPoint joinPoint, Object result) {
String name = joinPoint.getSignature().getName(); // 获取方法名
log.info("返回通知: 开启调用,方法名:{}. 返回结果:{}", name, result);
}
@AfterThrowing(value = "pointcut()", throwing = "e")
public void AfterThrowing(JoinPoint joinPoint, Exception e) {
String name = joinPoint.getSignature().getName(); // 获取方法名
log.info("异常通知: 开启调用,方法名:{}. 异常:{}", name, e.getMessage());
}
@Around(value = "pointcut()")
public Object Around(ProceedingJoinPoint pjp) {
String name = null; // 获取方法名
Object result = null;
try {
name = pjp.getSignature().getName();
log.info("环绕通知--前置: 开启调用,方法名:{}", name);
result = pjp.proceed();
log.info("环绕通知--返回: 开启调用,方法名:{}", name);
} catch (Exception e) {
throw new RuntimeException(e);
} catch (Throwable e) {
log.info("环绕通知--异常: 开启调用,方法名:{}", name);
} finally {
log.info("环绕通知--后置: 开启调用,方法名:{}", name);
}
return result;
}
}
调用接口测试
修改controller(需异常)
package com.luojie.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class AOPTestController {
@GetMapping("/aop/test")
public Object testAOP(@RequestParam("ab") int a, @RequestParam(value = "sr", required = false) String b) {
int i = b.compareTo(String.valueOf(a));
return i > 0 ? b : a;
}
}
调接口时,不传入sr
查看结果
总结
请自行测试总结每个通知执行的先后顺序!