侧边栏壁纸
博主头像
WeDaily 博主等级

不以物喜,不以己悲。心之所向,素履以往。

  • 累计撰写 22 篇文章
  • 累计创建 26 个标签
  • 累计收到 2 条评论

目 录CONTENT

文章目录

Spring AOP

奥德虎
2024-09-23 / 0 评论 / 0 点赞 / 32 阅读 / 0 字 / 正在检测是否收录...
温馨提示:
部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

#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

表达式用于匹配带有特定注解的方法或其他连接点,通常与 executionexecution 组合使用。

@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 {
       ...
    }
}

其它

  • 除了以上几种外,还有 thistargetargs 等几种匹配方式。
  • 多个 表达式 或多个 切入点 可以通过 &&|| 进行组合使用,也可以通过 () 组成复杂的表达式(但一般不建议)。
@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 {
	    ...
	 }
}

备注:logAround1logAround2 是等效的!

通知类型

Spring AOP(面向切面编程)提供了多种类型的通知(advice),这些通知定义了在切入点(pointcut)匹配的连接点(join point)处执行的操作。以下是 Spring AOP 中最常用的通知类型:
Spring AOP-20240921121013.png

  • 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 注解标记一个日志切面。此时,methodAmethodB 会触发日志切面,而 methodC 则不会触发!
通常情况下,可以通过 属性注入ApplicationContextAopContext 几种方式获取 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)

0

评论区