AOP快速上手

本文详细介绍了AOP(面向切面编程)在Spring框架中的应用,包括如何快速入门,核心概念如连接点、通知和切入点,以及Spring中不同类型的通知和切入点表达式的使用。
摘要由CSDN通过智能技术生成

AOP

  • AOP(Aspect Oriented Programming):面向特定方法编程

1. 快速入门

  1. 导入依赖spring-boot-starter-aop
        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
            <version>3.2.0</version>
        </dependency>
  1. 编写 AOP 类
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;


@Component // 注入到 Spring 容器
@Aspect // AOP 类
@Slf4j
public class TimeAspect {
    @Around("execution(* com.axr.service.*.*(..))") // 切入点表达式

    public Object recordTime(ProceedingJoinPoint joinPoint) throws Throwable {
        // 记录时间
        long begin = System.currentTimeMillis();
        // 执行方法
        Object result = joinPoint.proceed();
        // 记录时间
        long end = System.currentTimeMillis();
        log.info(joinPoint.getSignature() + "方法 {} 执行时间:{}ms", joinPoint.getSignature().getName(), end - begin);

        return result;
    }
}

当调用 com.axr.service包下的方法时:就会记录方法运行的时间

2. 核心概念

  • 连接点: JoinPoint,连接点指的是可以被aop控制的方法。(如上文当中所有的业务方法)
  • 通知:Advice,共性功能。

上文代码中是需要统计各个业务方法的执行耗时的,此时我们就需要在这些业务方法运行开始之前,先记录这个方法运行的开始时间,在每一个业务方法运行结束的时候,再来记录这个方法运行的结束时间。

​ 但是在AOP面向切面编程当中,我们只需要将这部分重复的代码逻辑抽取出来单独定义。抽取出来的这一部分重复的逻辑,也就是共性的功能。

  • 切入点:PointCut**,匹配连接点的条件,通知仅会在切入点方法执行时被应用。(Around(切入表达式))
  • 切面: Aspect,描述通知与切入点的对应关系
  • 目标对象:Target,通知所应用的对象

3. 通知

Spring中AOP的通知类型:

  • @Around:环绕通知,此注解标注的通知方法在目标方法前、后都被执行
  • @Before:前置通知,此注解标注的通知方法在目标方法前被执行
  • @After :后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行
  • @AfterReturning : 返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行
  • @AfterThrowing : 异常后通知,此注解标注的通知方法发生异常后执行

4. 通知顺序

  1. 不同的切面类当中,默认情况下通知的执行顺序是与切面类的类名字母排序是有关系的
  2. 可以在切面类上面加上@Order注解,来控制不同的切面类通知的执行顺序

5. 切入点表达式

1. @execution(…)

execution主要根据方法的返回值、包名、类名、方法名、方法参数等信息来匹配,语法为:

execution(访问修饰符?  返回值  包名.类名.?方法名(方法参数) throws 异常?)

其中带?的表示可以省略的部分

  • 访问修饰符:可省略(比如: public、protected)

  • 包名.类名: 可省略

  • throws 异常:可省略(注意是方法上声明抛出的异常,不是实际抛出的异常)

@Before("execution(void com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer))")

可以使用通配符描述切入点

  • * :单个独立的任意符号,可以通配任意返回值、包名、类名、方法名、任意类型的一个参数,也可以通配包、类、方法名的一部分
execution(* com.*.service.*update*(*))
  • .. :多个连续的任意符号,可以通配任意层级的包,或任意类型、任意个数的参数
execution(* com.syy..DeptSerive.*(..))
2. @annotation

匹配标识有特定注解的方法

实现步骤:

  1. 编写自定义注解

  2. 在业务类要做为连接点的方法上添加自定义注解

首先,我们需要定义一个自定义的注解,让我们称其为 @CustomAnnotation

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomAnnotation {
    String value() default "";
}

然后,我们创建一个业务类 BusinessClass,其中包含带有 @CustomAnnotation 注解的方法:

public class BusinessClass {
    @CustomAnnotation("someValue")
    public void annotatedMethod() {
        System.out.println("Annotated method is executed");
    }

    public void nonAnnotatedMethod() {
        System.out.println("Non-annotated method is executed");
    }
}

接下来,我们编写一个切面 CustomAspect 来拦截带有 @CustomAnnotation 注解的方法:

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class CustomAspect {

    @Before("@annotation(customAnnotation)")
    public void beforeAnnotatedMethod(JoinPoint joinPoint, CustomAnnotation customAnnotation) {
        System.out.println("Before execution of method annotated with @CustomAnnotation");
        System.out.println("Value: " + customAnnotation.value());
    }
}

最后,我们创建一个 Main 类来测试这些代码:

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(CustomAspect.class);
        context.refresh();

        BusinessClass businessClass = context.getBean(BusinessClass.class);
        businessClass.annotatedMethod();
        businessClass.nonAnnotatedMethod();

        context.close();
    }
}

在这个例子中,当我们调用 businessClass.annotatedMethod() 方法时,AspectJ 切面 CustomAspect 将会拦截这个调用,并在方法执行之前输出相应的信息。而对于 businessClass.nonAnnotatedMethod() 方法,由于没有被 @CustomAnnotation 注解修饰,所以不会被切面拦截

6. 连接点

在Spring中用JoinPoint抽象了连接点,用它可以获得方法执行时的相关信息,如目标类名、方法名、方法参数等。

  • 对于@Around通知,获取连接点信息只能使用ProceedingJoinPoint类型

  • 对于其他四种通知,获取连接点信息只能使用JoinPoint,它是ProceedingJoinPoint的父类型

案例如下:

首先,我们定义一个简单的服务类 MyService

package com.example;

public class MyService {
    public void myMethod(String arg) {
        System.out.println("Executing myMethod with argument: " + arg);
    }
}

然后,我们创建一个切面 MyAspect,在该切面中编写不同类型的通知以获取连接点信息:

package com.example;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;

@Aspect
public class MyAspect {

    @Before("execution(* com.example.MyService.*(..))")
    public void beforeAdvice(JoinPoint joinPoint) {
        System.out.println("Before advice:");
        System.out.println("Target class: " + joinPoint.getTarget().getClass().getName());
        System.out.println("Method name: " + joinPoint.getSignature().getName());
        System.out.println("Arguments: ");
        Object[] args = joinPoint.getArgs();
        for (Object arg : args) {
            System.out.println(arg);
        }
    }

    @AfterReturning(pointcut = "execution(* com.example.MyService.*(..))", returning = "result")
    public void afterReturningAdvice(JoinPoint joinPoint, Object result) {
        System.out.println("After returning advice:");
        System.out.println("Target class: " + joinPoint.getTarget().getClass().getName());
        System.out.println("Method name: " + joinPoint.getSignature().getName());
        System.out.println("Returned value: " + result);
    }

    @AfterThrowing(pointcut = "execution(* com.example.MyService.*(..))", throwing = "ex")
    public void afterThrowingAdvice(JoinPoint joinPoint, Throwable ex) {
        System.out.println("After throwing advice:");
        System.out.println("Target class: " + joinPoint.getTarget().getClass().getName());
        System.out.println("Method name: " + joinPoint.getSignature().getName());
        System.out.println("Exception: " + ex.getMessage());
    }

    @After("execution(* com.example.MyService.*(..))")
    public void afterAdvice(JoinPoint joinPoint) {
        System.out.println("After advice:");
        System.out.println("Target class: " + joinPoint.getTarget().getClass().getName());
        System.out.println("Method name: " + joinPoint.getSignature().getName());
    }

    @Around("execution(* com.example.MyService.*(..))")
    public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("Around advice - before proceeding:");
        System.out.println("Target class: " + proceedingJoinPoint.getTarget().getClass().getName());
        System.out.println("Method name: " + proceedingJoinPoint.getSignature().getName());
        System.out.println("Arguments: ");
        Object[] args = proceedingJoinPoint.getArgs();
        for (Object arg : args) {
            System.out.println(arg);
        }

        Object result = proceedingJoinPoint.proceed();

        System.out.println("Around advice - after proceeding:");
        System.out.println("Returned value: " + result);

        return result;
    }
}

最后,我们创建一个应用程序 MainApp,使用Spring配置来启用AOP:

package com.example;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan(basePackages = "com.example")
public class MainApp {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainApp.class);

        MyService myService = context.getBean(MyService.class);
        myService.myMethod("test");

        context.close();
    }
}

在这个例子中,我们定义了一个简单的 MyService 类,并在切面 MyAspect 中编写了不同类型的通知来获取连接点信息。然后,在 MainApp 类中启用了Spring的AOP功能,并使用 MyService 的实例来调用 myMethod() 方法。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值