Spring AOP

简介: Spring AOP

引言



AOP ( Aspect Oriented Programming ) :面向切面编程


AOP 是一种思想,表示对某一类事情的集中处理。Spring AOP 是一个框架,它是对 AOP 思想的实现。


为什么要使用 AOP 思想?


想象一个场景,我们在做用户验证的时候,除了登录和注册不需要做用户验证之外,几乎其他所有页面都需要先验证用户登录的状态,验证成功后,后面的代码才能继续走下去。以往我们的处理方式,是将每个 Controller 都要写一遍用户验证,然而当功能越来越多的时候,即 Controller 越多的时候,那么我们要写的登录验证也越来越多,而这些方法又是相同的,这么多的方法就会增加代码修改和维护的成本。此时,我们就可以考虑利用 AOP 来统一处理了。


而对于功能统一,且使用地方较多的功能,就可以优先考虑 AOP 思想。


一、AOP 组成



5557cc268f504ac2b27a12872c215273.png


1. 切面 (Aspect)

切面包含了通知、切点和切面的类,相当于 AOP 实现某个功能的集合。


2. 连接点 (Join Point)

所有可能触发 AOP 的页面 / 数据,可以被称为连接点。


3. 切点 (Pointcut)

切点相当于保存了众多连接点的一个集合,如果把切点看作成一个表格,那么连接点就是表格中的一个个数据。另外,切点相当于增强的方法。


4. 通知 (Advice)

通知规定了 AOP 执行时机和执行方法。切面的工作就被称之为通知。


AOP 有五种通知:


① 前置通知

② 后置通知

③ 抛出异常之后的通知

④ 返回数据之后的通知

⑤ 环绕通知


二、Spring AOP 的使用



步骤1 添加依赖


由于在创建 Spring Boot 的时候,页面没有为我们提供 AOP 依赖,所以我们在 maven 仓库中,搜索如下依赖,并添加至 pom.xml 文件中。


d328772bd5f840beadcef17e4b80d2f6.png


<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-aop</artifactId>
  <version>2.7.3</version>
</dependency>


步骤2 添加切面和切点


c6f079135eed421a8669a99fcb3afa78.png


切点指的是具体要处理的某一类问题,比如用户登录权限验证就是一个具体的问题;记录所有方法的执行日志就是一个具体的问题。


@Aspect // 当前类是一个切面
@Component
public class UserAspect {
    // 定义一个切点(设置拦截规则)
    @Pointcut("execution(* com.example.demo.controller.UserController.*(..))")
    public void point1() {
    }
}


注意:


① 在代码中,我们通过 " @Pointcut " 这个注解来定义一个切点,并在注解的括号中注明表达式,表示拦截规则。


② point1 方法为空方法,它并不需要有方法体,它本身的方法名只是作为一个 " 标识 " 作用,这样后面的 Advice 通知就可以使用当前的切点。( 根据不同的拦截规则,切点可以设置多个 )


③ " @Pointcut " 注解后面的表达式遵循了 AspectJ 语法。


1b3e612375784bffbff1f952ad033df0.png

AspectJ 语法


execution() 是最常用的切点函数,用来匹配方法,比方说,我们需要对非法用户进行拦截,那么就可以在后端的方法代码中,利用 execution() 函数进行拦截,匹配到哪些方法,哪些方法就需要用来拦截非法用户。


execution(<修饰符><返回类型><包.类.方法(参数)><异常>)


其中,返回类型、方法、参数都不能省略。


AspectJ 支持的三种通配符:


*  : 匹配任意字符,只匹配一个元素(包,或类,或方法,或方法参数)
.. : 匹配任意字符,可以匹配多个元素,在表示类时,必须和 * 联合使用
+  : 表示按照类型匹配指定类本身包括其所有子类,必须跟在类名后面


示例:


execution(* com.cad.demo.User.*(..)) :匹配 User 类里的所有方法
execution(* com.cad.demo.User+.*(..)) :匹配该类的子类包括该类的所有方法
execution(* com.cad.*.*(..)) :匹配 com.cad 包下的所有类的所有方法
execution(* com.cad..*.*(..)) :匹配 com.cad 包下、子孙包下所有类的所有方法
execution(* addUser(String, int)) :匹配 addUser 方法,且第一个参数类型是 String,第二个参数类型是 int.


步骤3 添加 Advice 通知


在 UserAspect 类中,实现切面:


@Aspect // 当前类是一个切面
@Component
public class UserAspect {
    // 定义一个切点(设置拦截规则)
    @Pointcut("execution(* com.example.demo.controller.UserController.*(..))")
    public void point1() {
    }
    // 1. 定义 point1 切点的前置通知
    @Before("point1()") // 选择 point1 切点,即遵循其拦截规则
    public void doBefore() {
        System.out.println("前置通知:被执行了");
    }
    // 2. 定义 point1 切点的后置通知
    @After("point1()")
    public void doAfter() {
        System.out.println("后置通知:被执行了");
    }
    // 3. 定义 point1 切点的返回之后通知
    @AfterReturning("point1()")
    public void doAfterReturning() {
        System.out.println("执行了 AfterReturning 方法");
    }
    // 4. 定义 point1 切点的异常通知
    @AfterThrowing("point1()")
    public void doAfterThrowing() {
        System.out.println("执行了 AfterThrowing 方法");
    }
    // 5.定义 point1 切点的环绕通知
    @Around("point1()")
    public Object doAround(ProceedingJoinPoint joinPoint) {
        Object result = null;
        System.out.println("环绕通知:开始");
        try {
            // 执行目标方法,以及目标方法所对应的相应的通知
            result = joinPoint.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        System.out.println("环绕通知:结束");
        return result;
    }
}


在 UserController 类中,实现与前端交互:


@RestController
public class UserController {
    @RequestMapping("/hello1")
    public String hello1() {
        System.out.println("hello1 方法被执行了");
        return "你好,世界!";
    }
    @RequestMapping("/hello2")
    public String hello2() {
        System.out.println("hello2 方法被执行了");
        int a = 10 / 0;
        return "你好,世界!";
    }
}


前端访问 hello1 与 hello2 路径后,查看 IDEA 控制台:


6aaa97410cb74f7eaf31ae803b3acd9b.png


在上面的输出结果中,我们可以清晰地看到五个 Advice 通知的执行顺序。鉴于此,我们就可以在后端的代码中,对前端传来的参数进行一些处理,亦可以对后端即将要返回的数据进行优化。


比方说登录验证,我们就可以在前置通知中,设置一个拦截器,也就是说在前端与后端交互的 Controller 层之前,就可以直接拦截非法用户。


比方说异常处理,如果用户输入了一个参数,导致服务器异常,而此异常后端并没有事先意料到,我们就可以在后置通知中,将原本 500 的错误状态码封装成一个 json 数据给前端,前端再拿此 json 数据优化成一个用户看得懂的保存信息。(这是一件非常有意义的事情,因为用户并不是程序员,他们根本看不懂后端的错误提示,甚至连前端拿到 500 这样的错误信息,也不知道后端出现了什么问题。)


三、使用 AOP 统计某个类中每个方法的执行时间



对上面的代码做一些修改:


在 UserAspect 类中,实现切面:


@Aspect // 当前类是一个切面
@Component
public class UserAspect {
    // 定义一个切点(设置拦截规则)
    @Pointcut("execution(* com.example.demo.controller.UserController.*(..))")
    public void point1() {
    }
    // 使用 AOP 统计 UserController 每个方法的执行时间
    @Around(("point1()"))
    public Object doAround2(ProceedingJoinPoint joinPoint) {
        // Spring 框架提供的 StopWatch,类似于 System.currentTimeMillis() 时间戳
        StopWatch stopWatch = new StopWatch();
        Object result = null;
        try {
            stopWatch.start();
            // 执行目标方法,以及目标方法所对应的相应的通知
            result = joinPoint.proceed();
            stopWatch.stop();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        System.out.println(joinPoint.getSignature().getDeclaringType() + "." +
                            joinPoint.getSignature().getName() + " 方法花费的时间" +
                            stopWatch.getTotalTimeMillis()+ "ms");
        return result;
    }
}


在 UserController 类中,实现与前端交互:


@RestController
public class UserController {
    @RequestMapping("/hello2")
    public String hello2() {
        System.out.println("hello2 方法被执行了");
        return "你好,世界!";
    }
    @RequestMapping("/hello3")
    public String hello3() throws InterruptedException {
        Thread.sleep(3000);
        System.out.println("hello3 方法被执行了");
        return "hello, world!";
    }
}



2d10d29fc5694f4f809756415ffd52d7.png


四、Spring AOP 的实现原理



Spring AOP 是构建在动态代理基础上,因此 Spring 对 AOP 的支持局限于方法级别的拦截。我们知道,Spring Boot 单元测试的最小单元是方法级别的,所以可以看见,Spring AOP 的拦截功能做到了非常精准。


如下图所示,以往我们前端直接就可以与后端交互了,但是这就会带来安全隐患,当有 AOP 作为后端代理后,它就会在前端访问时,进行一些校验和拦截,显然数据在传输过程中更加安全。


994e809df7d742698af054ab05aa96a3.png


Spring AOP 动态代理实现的技术


1. JDK Proxy (JDK 动态代理)

2. GGLB Proxy


JDK Proxy 是官方提供的动态代理,但实际上它并不好用,所有 Spring AOP 一般来说会优先使用后者。当后者无法正常使用时,才会采用前者。


需要明确,GGLB Proxy 是通过继承代理对象来实现动态代理的,即子类拥有父类的所有功能的方式。但它不能代理最终类 (被final 修饰的类)。


目录
打赏
0
0
0
0
1
分享
相关文章
Spring AOP实现原理
本内容主要介绍了Spring AOP的核心概念、实现机制及代理生成流程。涵盖切面(Aspect)、连接点(Join Point)、通知(Advice)、切点(Pointcut)等关键概念,解析了JDK动态代理与CGLIB代理的原理及对比,并深入探讨了通知执行链路和责任链模式的应用。同时,详细分析了AspectJ注解驱动的AOP解析过程,包括切面识别、切点表达式匹配及通知适配为Advice的机制,帮助理解Spring AOP的工作原理与实现细节。
Spring Boot中的AOP实现
Spring AOP(面向切面编程)允许开发者在不修改原有业务逻辑的情况下增强功能,基于代理模式拦截和增强方法调用。Spring Boot通过集成Spring AOP和AspectJ简化了AOP的使用,只需添加依赖并定义切面类。关键概念包括切面、通知和切点。切面类使用`@Aspect`和`@Component`注解标注,通知定义切面行为,切点定义应用位置。Spring Boot自动检测并创建代理对象,支持JDK动态代理和CGLIB代理。通过源码分析可深入了解其实现细节,优化应用功能。
268 6
Spring AOP—通知类型 和 切入点表达式 万字详解(通俗易懂)
Spring 第五节 AOP——切入点表达式 万字详解!
255 25
|
5月前
|
Spring AOP—深入动态代理 万字详解(通俗易懂)
Spring 第四节 AOP——动态代理 万字详解!
146 24
|
4月前
|
微服务——SpringBoot使用归纳——Spring Boot中的切面AOP处理——Spring Boot 中的 AOP 处理
本文详细讲解了Spring Boot中的AOP(面向切面编程)处理方法。首先介绍如何引入AOP依赖,通过添加`spring-boot-starter-aop`实现。接着阐述了如何定义和实现AOP切面,包括常用注解如`@Aspect`、`@Pointcut`、`@Before`、`@After`、`@AfterReturning`和`@AfterThrowing`的使用场景与示例代码。通过这些注解,可以分别在方法执行前、后、返回时或抛出异常时插入自定义逻辑,从而实现功能增强或日志记录等操作。最后总结了AOP在实际项目中的重要作用,并提供了课程源码下载链接供进一步学习。
196 0
微服务——SpringBoot使用归纳——Spring Boot中的切面AOP处理——什么是AOP
本文介绍了Spring Boot中的切面AOP处理。AOP(Aspect Oriented Programming)即面向切面编程,其核心思想是分离关注点。通过AOP,程序可以将与业务逻辑无关的代码(如日志记录、事务管理等)从主要逻辑中抽离,交由专门的“仆人”处理,从而让开发者专注于核心任务。这种机制实现了模块间的灵活组合,使程序结构更加可配置、可扩展。文中以生活化比喻生动阐释了AOP的工作原理及其优势。
93 0
Spring Boot 3 集成Spring AOP实现系统日志记录
本文介绍了如何在Spring Boot 3中集成Spring AOP实现系统日志记录功能。通过定义`SysLog`注解和配置相应的AOP切面,可以在方法执行前后自动记录日志信息,包括操作的开始时间、结束时间、请求参数、返回结果、异常信息等,并将这些信息保存到数据库中。此外,还使用了`ThreadLocal`变量来存储每个线程独立的日志数据,确保线程安全。文中还展示了项目实战中的部分代码片段,以及基于Spring Boot 3 + Vue 3构建的快速开发框架的简介与内置功能列表。此框架结合了当前主流技术栈,提供了用户管理、权限控制、接口文档自动生成等多项实用特性。
319 8
Spring Aop该如何使用
本文介绍了AOP(面向切面编程)的基本概念和术语,并通过具体业务场景演示了如何在Spring框架中使用Spring AOP。文章详细解释了切面、连接点、通知、切点等关键术语,并提供了完整的示例代码,帮助读者轻松理解和应用Spring AOP。
233 2
Spring Aop该如何使用
什么是AOP?如何与Spring Boot一起使用?
什么是AOP?如何与Spring Boot一起使用?
207 5
深入解析:Spring AOP的底层实现机制
在现代软件开发中,Spring框架的AOP(面向切面编程)功能因其能够有效分离横切关注点(如日志记录、事务管理等)而备受青睐。本文将深入探讨Spring AOP的底层原理,揭示其如何通过动态代理技术实现方法的增强。
280 8
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等

登录插画

登录以查看您的控制台资源

管理云资源
状态一览
快捷访问