4.12 SpringBoot整合AOP ❤❤❤

1. AOP简介

要介绍面向切面编程(Aspect-Oriented Programming,AOP),需要读者
首先考虑这样一个场景:
公司有一个人力资源管理系统目前已经上线,但是系统运行不稳定,有时运行得很慢,为了检测出到底是哪个环节出问题了,开发人员想要监控每一个方法的执行时间,再根据这些执行时间判断出问题所在。当问题解决后,再把这些监控移除掉
系统目前已经运行,如果手动修改系统中成千上万个方法,那么工作量未免太大,而且这些监控方法以后还要移除掉;如果能够在系统运行过程中动态添加代码,就能很好地解决这个需求
这种在系统运行时动态添加代码的方式称为面向切面编程(AOP)
Spring框架对AOP提供了很好的支持。在AOP中,有一些常见的概念需要读者了解。

  • Joinpoint(连接点): 类里面可以被增强的方法即为连接点。例如,想修改哪个方法的功能,那么该方法就是一个连接点。
  • Pointcut(切入点): 对Joinpoint进行拦截的定义即为切入点。例如,拦截所有以insert开始的方法,这个定义即为切入点。
  • Advice(通知): 拦截到Joinpoint之后所要做的事情就是通知。例如,上文说到的打印日志监控。通知分为前置通知、后置通知、异常通知、最终通知和环绕通知。
  • Aspect(切面):Pointcut和Advice的结合。
  • Target(目标对象):要增强的类称为Target。

2. SpringBoot支持

SpringBoot在Spring的基础上对AOP的配置提供了自动化配置解决方案spring-boot-starter-aop,使开发者能够更加便捷地在SpringBoot项目中使用AOP。
配置步骤如下。

2.1 AOP依赖

首先在SpringBootWeb项目中引入spring-boot-starter-aop依赖,代码如下:

        <!-- SpringBoot 拦截器 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

2.2 定义切面

接下来创建切面,代码如下:

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class LogAspect1 {
    /**
     * 切入点为:com.ruoyi.system.service包下的所有类的所有方法
     */
    @Pointcut("execution(* com.ruoyi.system.service.*.* (..))")
    public void pc1() {
    }

    /**
     * 前置通知,对切入点pc1()进行增强
     *
     * @param jp 连接点:包含增强的方法信息
     */
    @Before(value = "pc1()")
    public void before(JoinPoint jp) {
        String name = jp.getSignature().getName();
        System.out.println("Before 前置通知 >>" + name + "方法开始执行...");
    }

    /**
     * 后置通知,对切入点pc1()进行增强
     *
     * @param jp 连接点:包含增强的方法信息
     */
    @After(value = "pc1()")
    public void after(JoinPoint jp) {
        String name = jp.getSignature().getName();
        System.out.println("After 后置通知 >>" + name + "方法结束执行...");
    }

    /**
     * 返回通知,对切入点pc1()进行增强
     *
     * @param jp     连接点:包含增强的方法信息
     * @param result 方法返回值
     */
    @AfterReturning(value = "pc1()", returning = "result")
    public void afterReturning(JoinPoint jp, Object result) {
        String name = jp.getSignature().getName();
        System.out.println("AfterReturning 返回通知 >>" + name + "方法返回值:" + result);
    }

    /**
     * 异常通知,对切入点pc1()进行增强
     *
     * @param jp 连接点:包含增强的方法信息
     * @param e  抛出的异常信息
     */
    @AfterThrowing(value = "pc1()", throwing = "e")
    public void afterReturning(JoinPoint jp, Exception e) {
        String name = jp.getSignature().getName();
        System.out.println("AfterThrowing 异常通知 >>" + name + "方法抛出异常,异常信息" + e.getMessage());
    }

    /**
     * 环绕通知
     *
     * @param pjp
     * @return
     * @throws Throwable
     */
    @Around("pc1()")
    public Object around(ProceedingJoinPoint pjp) {
        String name = pjp.getSignature().getName();
        System.out.println("Around 环绕通知 >>" + name + "方法开始执行...");
        try {
            return pjp.proceed();
        } catch (Throwable e) {
            System.out.println("Around 环绕通知 >>" + name + "方法发生异常...");
            throw new RuntimeException(e);
        } finally {
            System.out.println("Around 环绕通知 >>" + name + "方法结束执行...");
        }
    }

}

代码解释:

  • @Aspect注解表明这是一个切面类。
  • 第12~14行定义的pc1方法使用了@Pointcut注解,这是一个切入点定义。execution中的第一个*表示方法返回任意值,第二个*表示service包下的任意类,第三个*表示类中的任意方法,括号中的两个点表示方法参数任意,即这里描述的切入点为service包下所有类中的所有方法。
  • 第21~25行定义的方法使用了@Before注解,表示这是一个前置通知,该方法在目标方法执行之前执行。通过JoinPoint参数可以获取目标方法的方法名、修饰符等信息。
  • 第32~36行定义的方法使用了@After注解,表示这是一个后置通知,该方法在目标方法执行之后执行。
  • 第44~48行定义的方法使用了@AfterReturning注解,表示这是一个返回通知,在该方法中可以获取目标方法的返回值。@AfterReturning注解的returning参数是指返回值的变量名,对应方法的参数。注意,在方法参数中定义了result的类型为Object,表示目标方法的返回值可以是任意类型,若result参数的类型为Long,则该方法只能处理目标方法返回值为Long的情况。
  • 第56~60行定义的方法使用了@AfterThrowing注解,表示这是一个异常通知,即当目标方法发生异常时,该方法会被调用,异常类型为Exception表示所有的异常都会进入该方法中执行,若异常类型为ArithmeticException,则表示只有目标方法抛出的ArithmeticException异常才会进入该方法中处理。
  • 第69~81行定义的方法使用了@Around注解,表示这是一个环绕通知。环绕通知是所有通知里功能最为强大的通知,可以实现前置通知、后置通知、异常通知以及返回通知的功能。目标方法进入环绕通知后,通过调用ProceedingJoinPoint对象的proceed方法使目标方法继续执行,开发者可以在此修改目标方法的执行参数、返回值等,并且可以在此处理目标方法的异常。

配置完成
测试

17:17:35.368 [http-nio-8080-exec-1] INFO  o.a.c.c.C.[.[.[/] - [log,173] - Initializing Spring DispatcherServlet 'dispatcherServlet'
Around 环绕通知 >>selectUserList方法开始执行...
Before 前置通知 >>selectUserList方法开始执行...
17:17:35.623 [http-nio-8080-exec-1] DEBUG c.r.s.m.S.selectUserList_COUNT - [debug,135] - ==>  Preparing: SELECT count(0) FROM sys_user u LEFT JOIN sys_dept d ON u.dept_id = d.dept_id WHERE u.del_flag = '0'
17:17:35.628 [http-nio-8080-exec-1] DEBUG c.r.s.m.S.selectUserList_COUNT - [debug,135] - ==> Parameters: 
17:17:35.630 [http-nio-8080-exec-1] DEBUG c.r.s.m.S.selectUserList_COUNT - [debug,135] - <==      Total: 1
17:17:35.633 [http-nio-8080-exec-1] DEBUG c.r.s.m.S.selectUserList - [debug,135] - ==>  Preparing: select u.user_id, u.dept_id, u.nick_name, u.user_name, u.email, u.avatar, u.phonenumber, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark, d.dept_name, d.leader from sys_user u left join sys_dept d on u.dept_id = d.dept_id where u.del_flag = '0' LIMIT ?
17:17:35.634 [http-nio-8080-exec-1] DEBUG c.r.s.m.S.selectUserList - [debug,135] - ==> Parameters: 10(Integer)
17:17:35.637 [http-nio-8080-exec-1] DEBUG c.r.s.m.S.selectUserList - [debug,135] - <==      Total: 2
AfterReturning 返回通知 >>selectUserList方法返回值:Page{count=true, pageNum=1, pageSize=10, startRow=0, endRow=10, total=2, pages=1, reasonable=true, pageSizeZero=false}
After 后置通知 >>selectUserList方法结束执行...
Around 环绕通知 >>selectUserList方法结束执行...

异常测试

Around 环绕通知 >>selectErr方法开始执行...
Before 前置通知 >>selectErr方法开始执行...
AfterThrowing 异常通知 >>selectErr方法抛出异常,异常信息null
After 后置通知 >>selectErr方法结束执行...
Around 环绕通知 >>selectErr方法发生异常...
Around 环绕通知 >>selectErr方法结束执行...
17:23:08.159 [http-nio-8080-exec-3] ERROR c.r.f.w.e.GlobalExceptionHandler - [handleRuntimeException,93] - 请求地址'/system/user/err',发生未知异常.
java.lang.RuntimeException: com.ruoyi.common.exception.ServiceException

**********************************************************

相关推荐

  1. 4.12 SpringBoot整合AOP

    2024-04-23 06:02:03       40 阅读
  2. 23.2 微服务SpringCloud基础实战()

    2024-04-23 06:02:03       42 阅读
  3. 16.3 Spring框架_SpringJDBC与事务管理()

    2024-04-23 06:02:03       54 阅读
  4. 资源概览

    2024-04-23 06:02:03       30 阅读
  5. 11.2 Web开发_CSS入门()

    2024-04-23 06:02:03       59 阅读
  6. 13.2 Web与Servlet进阶()

    2024-04-23 06:02:03       48 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-04-23 06:02:03       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-04-23 06:02:03       100 阅读
  3. 在Django里面运行非项目文件

    2024-04-23 06:02:03       82 阅读
  4. Python语言-面向对象

    2024-04-23 06:02:03       91 阅读

热门阅读

  1. 基础面试题

    2024-04-23 06:02:03       31 阅读
  2. linux centos6.4 升级centos7.2

    2024-04-23 06:02:03       40 阅读
  3. Tomcat设计思路

    2024-04-23 06:02:03       29 阅读
  4. <商务世界>《69 微课堂<工业BD是什么?>》

    2024-04-23 06:02:03       35 阅读
  5. 高斯锁表导致sql报错处理

    2024-04-23 06:02:03       32 阅读
  6. Rust语言入门第六篇-函数

    2024-04-23 06:02:03       31 阅读
  7. git简单实践

    2024-04-23 06:02:03       35 阅读
  8. 迭代加深搜索

    2024-04-23 06:02:03       32 阅读
  9. 基于TypeScript自定义Strapi users-permissions插件接口

    2024-04-23 06:02:03       49 阅读
  10. C# Promise对象详解

    2024-04-23 06:02:03       38 阅读