spring AOP 面向切面的理解与基本使用

本文介绍 Spring AOP 的核心概念,如切面、连接点、切点等,并通过实战案例展示了如何利用自定义注解和切面类实现方法的日志记录功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

基本概念

  • Aspect(切面): Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。
  • Joint point(连接点):表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。
  • Pointcut(切点):表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。
  • Advice(增强):Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。
  • Target(目标对象):织入 Advice 的目标对象.。
  • Weaving(织入):将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程

案例

添加一个自定义注解
package com.example.sano.Infrastructure;

import java.lang.annotation.*;

@Target(ElementType.METHOD)//这里标记该注解需要加到方法上,而不能是类或属性上
@Retention(RetentionPolicy.RUNTIME)
public @interface RecodeLog {

    public String value() default "";

    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface attr{
        String value();
    }

}

添加切面类
package com.example.sano.Infrastructure;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

@Slf4j
@Component
@Aspect
public class RecodeLogAspect {

	//这里我们的切点明确标记了是hello方法,然而一般情况我们的切点都是注解
    @Pointcut("execution(* com.example.sano.interfaces.HelloController.hello())")
    public void say(){
        System.out.println("pointcut ===========这是切点 ");
    }

	@Pointcut("@annotation(com.example.sano.Infrastructure.RecodeLog)")
    public void recodeLogPointcut() {
		System.out.println("pointcut ===========基于注解的切点 ");
	}

    @After("say()")
    public void after(){
        System.out.println("after ============后置通知");
    }

    @Before("execution(* com.example.sano.interfaces.HelloController.hello())")
    public void before(){
        System.out.println("before =========== 前置通知");
    }

	//使用基于注解的接口
    @Around("recodeLogPointcut()")
    public Object around(ProceedingJoinPoint pjp){
        System.out.println("around around=====环绕通知");

        Class<?> aClass = pjp.getTarget().getClass(); //先获取被织入增强处理的目标对象,再获取目标类的clazz
        String methodName = pjp.getSignature().getName(); //先获取目标方法的签名,再获取目标方法的名
        log.deubg("methodName: "+methodName);  // 输出目标方法名
        
        Class[] parameterTypes = ((MethodSignature) pjp.getSignature()).getParameterTypes(); //获取目标方法参数类型
        Object[] args = pjp.getArgs();  //获取目标方法的入参
        for (int i = 0; i < args.length; i++) {
            log.deubg("argsName: "+args[i]); //输出目标方法的参数
        }
        try {
            Method method = aClass.getMethod(methodName, parameterTypes);  //获取目标方法
            RecodeLog annotation = method.getAnnotation(RecodeLog.class);  //获取方法上的注解
            annotation.value();  //获取注解函数值
            
            Object proceed = pjp.proceed();  //执行目标方法
            
            log.deubg("proceed: "+proceed);  //打印目标方法的return结果
            return proceed;
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        return "aop的返回值";
    }

}


针对环绕注解入参的解释:Proceedingjoinpoint 继承了JoinPoint,在JoinPoint的基础上暴露出 proceed(), 这个方法是AOP代理链执行的方法。JoinPoint仅能获取相关参数,无法执行连接点。暴露出proceed()这个方法,就能支持 aop:around 这种切面(而其他的几种面只需要用到JoinPoint,这跟切面类型有关),就能控制走代理链还是走自己拦截的其他逻辑。

针对Signature的解释:Signature是 Java 反射 API 中的一个接口,用于描述方法或构造函数的签名信息。Spring AOP 中的 JoinPoint 接口继承了反射 API 中的 Member 接口,因此可以通过 JoinPoint 对象获取到方法或构造函数的 Signature 对象。
在 Spring AOP 中,切面可以通过 JoinPoint 对象获取到连接点的相关信息,其中包括方法签名信息,可以通过 JoinPoint 对象的 getSignature() 方法获取到 Signature 对象。如果连接点是一个方法,那么可以将 Signature 对象转换为 MethodSignature 对象,通过 MethodSignature 对象可以获取到更加详细的方法签名信息,比如方法返回类型、参数类型等。在 Spring AOP 中,JoinPoint 是连接点的表示,表示在程序执行过程中的特定点,比如方法调用、异常抛出等。JoinPoint 接口定义了许多方法,可以获取连接点的相关信息,其中 getSignature() 方法可以获取连接点处的方法签名信息。
Signature 接口是一个标记接口,它没有定义任何方法。但在其子接口 MethodSignature 中,定义了 getName() 方法,用于获取方法的名称。
MethodSignature 表示方法的签名信息,包括方法名称、返回类型、参数类型等。它定义了一些方法,可以获取方法的相关信息,包括 getName() 方法用于获取方法名称、getReturnType() 方法用于获取方法返回类型、getParameterTypes() 方法用于获取方法参数类型列表等。
在这里插入图片描述

在AspectJ中,Signature接口用于表示切面可以应用的程序元素的签名,比如方法、构造函数、字段等。CodeSignatureSignature的一个子接口,专门用于表示代码相关的签名,比如方法的代码块。以下是Signature接口及其子接口的一些实现类的含义,特别是CodeSignature的实现类:

  1. Signature接口及其实现类

    • MethodSignature:表示方法的签名,包括方法名、参数类型、返回值类型等。
    • ConstructorSignature:表示构造函数的签名,包括构造函数名(通常是类名)和参数类型。
    • FieldSignature:表示字段的签名,包括字段名和字段类型。
    • AdviceSignature:表示切面中通知(advice)的签名,比如@Before@After等通知方法。
    • InitializerSignature:表示初始化器的签名,通常用于静态或实例初始化块。
    • CodeSignature:是一个更通用的接口,用于表示代码块的签名,其实现类进一步细化了签名的类型。
  2. CodeSignature的实现类

    • AdviceSignatureImpl:实现了AdviceSignature接口,表示切面中通知方法的具体签名实现。它包含了通知方法的详细信息,如通知类型(前置、后置、环绕等)、方法参数等。
    • ConstructorSignatureImpl:实现了ConstructorSignature接口,是构造函数签名的具体实现。它提供了构造函数的具体信息,如参数列表、所属类等。
    • InitializerSignatureImpl:实现了InitializerSignature接口,是初始化器签名的具体实现。它用于表示类或实例的初始化块的签名。
    • MethodSignatureImpl:实现了MethodSignature接口,是方法签名的具体实现。它包含了方法的详细信息,如方法名、参数类型、返回值类型、访问修饰符等。

这些实现类在AspectJ的切面编程中起着关键作用,它们允许切面代码精确地识别和处理不同类型的程序元素,从而实现诸如日志记录、性能监控、事务管理等横切关注点的功能。通过这些签名对象,切面可以获取到目标程序元素的详细信息,进而在合适的时机执行相应的通知逻辑。

ps:针对注解的Taget枚举类描述
在这里插入图片描述

目标方法

@Slf4j
@Controller
public class HelloController {

    @RecodeLog
    @ResponseBody
    @RequestMapping("/hello")
    public String hello(){
        log.info("hello world");
        return "hello";
    }
}
最终结果

在这里插入图片描述

执行步骤
  1. 浏览器发送请求到controller 的hello方法
  2. 切面类根据切点拦截,
  3. 根据顺序依次执行around before after
总结

先打印了around,表示环绕通知是将目标方法包裹,所以先于目标方法执行,通过代码也能发现,环绕通知内根据反射获取目标方法,然后手动调用执行。然后环绕通知配合注解使用,拦截了标记RecordLog 注解的方法,在处理方法内获取了注解的参数。
然后通过反射获取到目标方法,然后手动调用执行,此时触发前置通知,打印前置通知,然后目标方法执行,输出hello world,然后触发后置通知,最后环绕通知处理目标方法返回值,并返回最终结果;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值