Spring AOP(基本认识与使用)

前言

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 切入点表达式的基本语法:

  1. 方法匹配符号

    • execution: 匹配方法执行连接点。

    • within: 匹配指定类型内的方法执行连接点。

    • this: 匹配当前代理对象类型的执行连接点。

    • target: 匹配目标对象类型的执行连接点。

    • args: 匹配方法参数类型的执行连接点。

  2. 通配符

    • *: 匹配任意数量字符。

    • ..: 匹配任意数量字符或子包。

  3. 运算符

    • &&: 逻辑与。

    • ||: 逻辑或。

    • !: 逻辑非。

  4. 类型模式

    • +: 子类型。

    • ..: 任意数量子包。

  5. 方法模式

    • *: 匹配任意名称的方法。

    • ():方法参数。

    • ..: 任意数量参数。

  6. 参数模式

    • *: 匹配任意类型的参数。

    • ..: 任意数量参数。

  7. 返回类型

    • 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

查看结果

总结

请自行测试总结每个通知执行的先后顺序!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值