#Spring
Spring AOP
配置
全局开并
[!note] 启用基于AspectJ的代理
@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)
- proxyTargetClass: 布尔值,指定是否使用 CGLIB 代理来代理所有类。默认值为
false
,意味着使用 JDK 动态代理。设置为 true 时,将使用 CGLIB 代理。 - exposeProxy: 用于控制是否暴露 AOP 代理对象给切面。默认值为 false,- 当 exposeProxy 设置为true时,切面中的方法可以通过特殊变量
AopContext.currentProxy()
访问到当前方法调用的代理对象。
切面定义
通过@Aspect 声明一个切面配置
@Aspect
@Component
public class LoggingAspect {
...
}
切点配置
execution
表达式用于匹配方法执行的连接点。它是最常用的切入点表达式之一,允许您指定要匹配的方法的签名
@Aspect
@Component
public class LoggingAspect {
@Around("execution(* com.example.UserService.**.*(..))")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
Object result = joinPoint.proceed();
return result;
}
}
备注: **
表示匹配任意深度、. (..
) 两个点表示匹配任意方法签名
within
表达式用于匹配特定类或类层次结构中的所有连接点。within
一般来说不能对方法签名进行过滤
@Aspect
public class LoggingAspect {
@Around("within(com.example.service..*)")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
...
}
}
备注:..
表示匹配任意深度
annotation
表达式用于匹配带有特定注解的方法或其他连接点,通常与 execution
或 execution
组合使用。
@Aspect
public class LoggingAspect {
@Around("@annotation(com.example.annotations.Logged)")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
...
}
}
备注:在使用 annotation 配置切面时,如果注解本身支持 List,则需要指向注解本身和 List(否则识别不到)。如:
...
@interface Logged{
...
@interface List {
Logged[] value();
}
}
@Aspect
public class LoggingAspect {
@Around("@annotation(com.example.annotations.Logged) || @annotation(com.example.annotations.Logged.List)")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
...
}
}
@Pointcut
pointcut(切入点)是一个非常重要的概念。它是定义何时何地执行切面中的通知(advice)的规则。换句话说,pointcut指定了哪些连接点(join points)会被切面所拦截。
@Aspect
public class LoggingAspect {
@Pointcut("@annotation(com.example.annotations.Logged)")
public void pointcutXxx(){
}
@Around("pointcutXxx()")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
...
}
}
其它
- 除了以上几种外,还有
this
、target
、args
等几种匹配方式。 - 多个
表达式
或多个切入点
可以通过&&
或||
进行组合使用,也可以通过()
组成复杂的表达式(但一般不建议)。
@Aspect
public class LoggingAspect {
@Pointcut("@annotation(com.example.annotations.Logged)")
public void pointcut1(){
}
@Pointcut("within(com.example.service..*)")
public void pointcut2(){
}
@Around("pointcut1() && pointcut2()")
public Object logAround1(ProceedingJoinPoint joinPoint) throws Throwable {
...
}
@Around("@annotation(com.example.annotations.Logged) && within(com.example.service..*)")
public Object logAround2(ProceedingJoinPoint joinPoint) throws Throwable {
...
}
}
备注:logAround1
和 logAround2
是等效的!
通知类型
Spring AOP(面向切面编程)提供了多种类型的通知(advice),这些通知定义了在切入点(pointcut)匹配的连接点(join point)处执行的操作。以下是 Spring AOP 中最常用的通知类型:
- Before Advice:在方法调用之前执行。
- After Returning Advice:在方法成功返回后执行。
- After Throwing Advice:在方法抛出异常后执行。
- After (Finally) Advice:无论方法是否正常返回还是抛出异常,都会执行。
- Around Advice:在方法调用之前和之后都执行,可以完全控制方法调用过程。
1. Before Advice(前置通知)
- 定义:在切入点匹配的方法调用之前执行的通知。
- 用途:用于在方法调用之前执行某些操作,如日志记录或权限检查。
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class LoggingAspect {
@Before("execution(* com.example.service.*.*(..))")
public void logBefore(JoinPoint joinPoint) {
System.out.println("Before method execution: " + joinPoint.getSignature().getName());
}
}
2. After Returning Advice(后置通知)
- 定义:在切入点匹配的方法成功返回之后执行的通知。
- 用途:用于在方法正常返回后执行某些操作,如关闭资源或日志记录。
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
@Aspect
public class LoggingAspect {
@AfterReturning(pointcut = "execution(* com.example.service.*.*(..))", returning = "result")
public void logAfterReturning(JoinPoint joinPoint, Object result) {
System.out.println("After method execution: " + joinPoint.getSignature().getName());
System.out.println("Result: " + result);
}
}
3. After Throwing Advice(异常抛出通知)
- 定义:在切入点匹配的方法抛出异常后执行的通知。
- 用途:用于在方法抛出异常后执行某些操作,如错误处理或日志记录。
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
@Aspect
public class LoggingAspect {
@AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))", throwing = "exception")
public void logAfterThrowing(JoinPoint joinPoint, Exception exception) {
System.out.println("Exception occurred in: " + joinPoint.getSignature().getName());
System.out.println("Exception: " + exception.getMessage());
}
}
4. After (Finally) Advice(最终通知)
- 定义:无论方法是否正常返回还是抛出异常,都会执行的通知。
- 用途:用于执行清理操作,类似于 try-catch-finally 语句中的 finally 块。
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
@Aspect
public class LoggingAspect {
@After("execution(* com.example.service.*.*(..))")
public void logAfter(JoinPoint joinPoint) {
System.out.println("Finally method executed: " + joinPoint.getSignature().getName());
}
}
5. Around Advice(环绕通知)
- 定义:在切入点匹配的方法调用之前和之后都执行的通知。
- 用途:用于完全控制方法调用的过程,可以决定是否继续执行方法调用或返回自定义的结果。
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
@Aspect
public class LoggingAspect {
@Around("execution(* com.example.service.*.*(..))")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("Before method execution: " + joinPoint.getSignature().getName());
Object result = joinPoint.proceed();
System.out.println("After method execution: " + joinPoint.getSignature().getName());
return result;
}
}
注意事项
确保通过 Bean 调用方法(保证执行植入逻辑)
在 Bean 内部直接调用或通过 this 调用当前 Bean 的方法均不会执行植入的切面代码。通常情况下,我们需要通过容器获取到 Spring 管理的 Bean,然后通过引用对象来执行。
@Component
public class UserService{
@Resource
private UserService self;
/**
入口服务方法
**/
@UserLogger
public void methodA(){
self.methodB();
methodC();
}
public void methodB(){
...
}
public void methodC(){
...
}
}
备注:若通过 @UserLogger
注解标记一个日志切面。此时,methodA
、methodB
会触发日志切面,而 methodC
则不会触发!
通常情况下,可以通过 属性注入
、ApplicationContext
、AopContext
几种方式获取 Bean。
@Component
public class UserService{
@Resource
private ApplicationContext context;
@Resource
private UserService self1;
private UserService self2;
private UserService self3;
@PostConstruct
public void init(){
self2 = context.getBean(this.getClass()); //context.getBean(UserService.class)
self3 = AopContext.getCurrentProxy();
}
}
备注:通过 AopContext 获取时需要开启@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true
)
评论区