springboot 整合 AOP切面编程

什么是AOP切面编程

所谓AOP就是一种编程范例,我们一般做一个项目,会分为很多个模块,这些模块中有很多不同的功能,功能中有很多不同的切入点,举个例子,现在我们有一个保存用户业务逻辑功能,我们需要在保存用户之前输入一句话,这句话必须在保存用户之前做到,一般对我们springboot项目中,针对这样的情况,我们就会在Service层的代码中加入一行,如下图所示:

===================保存用户业务逻辑=================
用户业务
UserService

  void save(User user)

  void delete(Integer id);

  User queryById(Integer id);

  ....


UserServiceImpl implement UserService

  void save(User user){
  	sout("=============");//业务功能-1
  	...
  	userDao.save(user);
  }

 

但是你想过没有,假设我们又要在删除用户的逻辑中也输出这样的一行内容呢?
我们是不是又要在业务逻辑层中又增加这样的一行呢?

  void delete(Integer id){
	sout("=============");//业务功能-1
	......
  	userDao.delete(id);
  }

那如果我们的其他模块的功能结构也需要这样这样的功能,那岂不是我们都要去加入这一行,这岂不是很麻烦吗?所以我们引入了AOP,我们能把这个要加入的功能,作为一个关注点切入进去,就不用交织在每个模块的代码中,而是作为一个单独的模块存在。
从这里我们就可以发现AOP解决了两个问题:一个是代码混乱,核心的业务逻辑代码还必须兼顾其他功能,这就导致不同功能的代码交织到一起,可读性很差。
第二,是代码分散问题。同一个功能代码分散在多个模块中,不易维护。

AOP中重要概念

切面

切面(Aspect)由切点(Pointcut)和通知(Advice)组成,它既包含了横切逻辑的定义,也包括了连接点的定义。

连接点

应用执行过程中能够插入切面的一个点,这个点可以是方法调用时,抛出异常时,甚至修改字段时。切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为。
总的来说,就是程序执行的某一刻的,在这个点可以做的额外操作。

通知

切面的工作被称之为通知。

切入点

切入点是用来描述连接点的,它决定了当前代码与连接点匹配

我们可以简单的把 AOP 理解为贯穿于方法之中,在方法执行前、执行时、执行后、返回值后、异常后要执行的操作。

springboot的切面编程的步骤

引入切面编程依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

开发附加操作(在springboot项目新建config配置)

/**
 * 自定义切面配置类
 */
@Configuration //代表这个类是spring的配置类
@Aspect //代表这个类是切面配置类
public class MyAspectConfig {
    //切面Aspect=Advice附加操作+Pointcut切入点

    @Before("execution(* com.demo.springbootaoptest.service.*.*(..))")//代表这是一个核心业务逻辑执行之前的附加操作,value属性表明切书店
    public void before(JoinPoint joinPoint){
        System.out.println("=========前置附加操作=========");
        System.out.println("当前执行目标类: "+ joinPoint.getTarget());
        System.out.println("当前执行目标类中方法: "+ joinPoint.getSignature().getName());
    }
    @After("execution(* com.demo.springbootaoptest.service.*.*(..))")
    public void after(){
        System.out.println("后置通知");
    }
    @Around("execution(* com.demo.springbootaoptest.service.*.*(..))")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
    System.out.println("==========进入环绕的前置操作===========");
    System.out.println("当前执行类: "+proceedingJoinPoint.getTarget());
    System.out.println("方法名: "+proceedingJoinPoint.getSignature().getName());
    //放心目标方法执行
    Object proceed = proceedingJoinPoint.proceed();//继续处理  业务逻辑方法执行
    System.out.println("==========进入环绕的后置操作===========");
    return null;
}  
}

我对这上面的注解一一进行解释

  • @Aspect 用来类上,代表这个类是一个切面
  • @Before 用在方法上代表这个方法是一个前置通知方法
  • @After 用在方法上代表这个方法是一个后置通知方法
  • @Around 用在方法上代表这个方法是一个环绕的方法

另外这个方法是 上述 的 execution() 方法,用于匹配需要被 AOP 切入的方法。

execution() 方法的参数有以下几个:

"*" 表示匹配任意类型的方法。
com.demo.springbootaoptest.service 表示匹配 com.demo.springbootaoptest.service 包下的所有类。
.* 表示匹配所有类的所有方法。
(..) 表示匹配方法的任意参数。

service类

public class UserService {
   public void save(String name){
//       System.out.println("===========================");//=》额外操作
        System.out.println("处理save核心业务逻辑,调用mapper");
    }
    public void delete(Integer id) {
        System.out.println("===========================");

        System.out.println("处理delete核心业务逻辑,调用mapper");

    }
    public void update(String name){
        System.out.println("处理update核心业务逻辑,调用mapper");


    }
    public String find(String name){
        System.out.println("处理find核心业务逻辑,调用mapper");
        return name;
    }
}

前置结果
在这里插入图片描述

后置结果
在这里插入图片描述

环绕结果
这里要特别解释一下环绕通知的流程

在方法执行前,打印一条日志,表示进入环绕的前置操作。
获取目标方法的执行类和方法名。
调用 proceed() 方法,让目标方法执行。
在方法执行后,打印一条日志,表示进入环绕的后置操作。
在这里插入图片描述

SpringBoot Aop 常用注解

常用注解

  • @Aspect:用于定义切面
  • @Before:通知方法会在目标方法调用之前执行
  • @After:通知方法会在目标方法返回或抛出异常后执行
  • @AfterReturning:通知方法会在目标方法返回后执行
  • @AfterThrowing:通知方法会在目标方法抛出异常后执行
  • @Around:通知方法会将目标方法封装起来
  • @Pointcut:定义切点表达式

切点表达式

execution(方法修饰符 返回类型 方法所属的包.类名.方法名称(方法参数))

例子
在AspectJ中,execution是一个常用的切入点表达式,用于匹配特定的方法执行。它可以根据方法的修饰符、返回类型、所属的包、类名以及方法名称等信息来定义切入点。以下是几个使用execution表达式的例子,以及对它们的解释:

  1. 匹配特定类中的方法

    execution(* com.example.MyClass.myMethod(..))
    

    这个表达式匹配com.example包中MyClass类里的myMethod方法。*表示任何修饰符,..表示任何参数。

  2. 匹配任何返回类型的方法

    execution(* *.methodName(..))
    

    这里第一个*代表任何返回类型,第二个*代表任何类,methodName是你想要匹配的方法名。

  3. 匹配特定包及子包下所有类的方法

    execution(* com.example.*.*(..))
    

    这个表达式匹配com.example包及其所有子包中所有类的方法。

  4. 匹配特定修饰符的方法

    execution(public * com.example.MyClass.myMethod(..))
    

    这个表达式仅匹配公开(public)的myMethod方法。

  5. 匹配特定参数类型的方法

    execution(* com.example.MyClass.myMethod(java.lang.String, int))
    

    这个表达式匹配MyClass中接受Stringint作为参数的myMethod方法。

  6. 匹配继承自特定类的类中的方法

    execution(* com.example.*+.myMethod(..))
    

    +表示匹配继承自com.example包中任何类的myMethod方法。

  7. 匹配注解了特定注解的方法

    execution(@com.example.MyAnnotation * *(..))
    

    这个表达式匹配任何被@com.example.MyAnnotation注解的方法。

  8. 匹配特定异常类型的抛出

    execution(* *.*(..) throws java.lang.Exception)
    

    这个表达式匹配任何可能抛出java.lang.Exception或其子类异常的方法。

  9. 组合使用多个条件

    execution(public * com.example.service.*.*(..))
    

    这个表达式匹配com.example.service包中公开的任何类的方法。

在AspectJ中,execution表达式提供了强大的灵活性,允许你定义非常具体或非常广泛的切入点,以实现面向切面编程(AOP)的各种需求。

引入AOP切面编程的步骤

1、添加日志信息封装类WebLog

public class WebLog {
    private final Long userId; // 用户ID
    private final String username; // 用户名
    private final String operation; // 操作
    private final Long time; // 时间

    // 构造函数
    public WebLog(Long userId, String username, String operation, Long time) {
        this.userId = userId;
        this.username = username;
        this.operation = operation;
        this.time = time;
    }

    // Getter方法
    public Long getUserId() {
        return userId;
    }

    public String getUsername() {
        return username;
    }

    public String getOperation() {
        return operation;
    }

    public Long getTime() {
        return time;
    }

    // toString方法,用于日志输出
    @Override
    public String toString() {
        return "WebLog{" +
                "userId=" + userId +
                ", username='" + username + '\'' +
                ", operation='" + operation + '\'' +
                ", time=" + time +
                '}';
    }
}

2、添加切面类WebLogAspect

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Aspect // 声明这是一个切面
@Component // 将切面类作为组件加入到Spring容器中
public class WebLogAspect {
    
    @Pointcut("execution(* com.example.controller.*.*(..))") // 定义一个切入点,匹配com.example.controller包下所有类的任何方法
    public void logForControllerPackage() {}

    @Before("logForControllerPackage()") // 定义前置通知,在切入点执行前执行
    public void beforeAdvice(JoinPoint joinPoint) {
        // 从连接点中提取信息
        String username = "user"; // 这里通常是从安全上下文中获取用户名
        Long userId = 1L; // 这里通常是从安全上下文中获取用户ID
        String operation = joinPoint.getSignature().getName(); // 获取方法名作为操作名称
        Long time = System.currentTimeMillis(); // 获取当前时间

        // 创建WebLog实例
        WebLog webLog = new WebLog(userId, username, operation, time);

        // 打印日志信息
        System.out.println(webLog.toString());
    }
}

3、测试类

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration // 声明这是一个配置类
@EnableAspectJAutoProxy // 启用对AspectJ自动代理的支持
public class AppConfig {

    @Bean // 声明一个Bean
    public WebLogAspect webLogAspect() {
        return new WebLogAspect();
    }

    @Bean
    public SomeController someController() {
        return new SomeController();
    }

    public static void main(String[] args) {
        // 创建Spring应用程序上下文
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        context.register(AppConfig.class);
        context.refresh();

        // 获取控制器Bean并执行一个动作
        SomeController controller = context.getBean(SomeController.class);
        controller.someMethod();

        // 关闭上下文
        context.close();
    }
}

// 一个示例控制器,将被WebLogAspect记录日志
class SomeController {
    public void someMethod() {
        // 方法逻辑
    }
}
  • 20
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

忘忧记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值