躺着敲代码第二天-----聊聊Spring的AOP(面向切面编程)

AOP(Aspect oriented programming)

什么是AOP

AOP(Aspect Oriented Programming)即面向切面编程,AOP 是 OOP(面向对象编程)的一种延续,二者互补,
并不对立。
AOP 的目的是将横切关注点(如日志记录、事务管理、权限控制、接口限流、接口幂等等)从核心业务逻辑中分离出来,
通过动态代理、字节码操作等技术,实现代码的复用和解耦,提高代码的可维护性和可扩展性。OOP 的目的是将业务逻辑
按照对象的属性和行为进行封装,通过类、对象、继承、多态等概念,实现代码的模块化和层次化(也能实现代码的复用),
提高代码的可读性和可维护性。

AOP为什么叫面向切面编程

AOP 之所以叫面向切面编程,是因为它的核心思想就是将横切关注点从核心业务逻辑中分离出来,形成一个个的切面(Aspect)。
躺着敲代码第二天-----聊聊Spring的AOP(面向切面编程)_第1张图片

AOP的执行流程

Spring AOP的执行流程是理解其工作原理的关键,它通过在程序运行时动态地将切面逻辑织入到目标对象中,从而实现横切关注点的分离。下面我们来详细地分析 Spring AOP的执行流程

配置切面

AOP的执行流程从配置切面开始,切面可以通过 XML配置文件或基于注解的方式进行定义。配置切面时,主要涉及以下几个元素:

  • 切面类(Aspect): 包含横切逻辑的类,通常用@Aspect注解标识。
  • 通知方法(Advice):定义在特定连接点上执行的横切逻辑。通知类型包括@Before、@After、@Around、@AfterReturning、@AfterThrowing等。
  • 切入点表达式(Pointcut Expression):用于匹配连接点的方法执行点,通常使用AspectJ的切入点表达式语法。

创建代理对象

在Spring容器启动时,Spring会扫描配置的切面类,并为每个目标对象创建代理对象。代理对象负责在目标方法执行前后插入切面逻辑。Spring AOP使用两种主要的代理方式:

  • JDK动态代理:适用于目标对象实现了接口的情况,通过Java的反射机制创建代理对象。
  • CGLIB代理:适用于目标对象没有实现接口的情况,通过生成目标类的子类来创建代理。

方法调用拦截

当客户端代码调用目标对象的方法时,实际上是通过代理对象来进行调用的。代理对象实现了与目标对象相同的接口,因此客户端代码无需感知代理的存在。

  • 拦截方法调用:代理对象拦截对目标方法的调用。对于JDK动态代理,这是通过实现InvocationHandler接口的invoke方法来实现的;对于CGLIB代理,这是通过生成子类并重写方法来实现的。

执行通知

躺着敲代码第二天-----聊聊Spring的AOP(面向切面编程)_第2张图片
在方法调用被拦截后,代理对象会根据切面配置执行相应的通知逻辑:

  • Before(前置通知):目标对象的方法调用之前触发
  • After (后置通知):目标对象的方法调用之后触发
  • AfterReturning(返回通知):目标对象的方法调用完成,在返回结果值之后触发
  • AfterThrowing(异常通知):目标对象的方法运行中抛出 / 触发异常后触发。AfterReturning 和 AfterThrowing 两者互斥。如果方法调用成功无异常,则会有返回值;如果方法抛出了异常,则不会有返回值。
  • Around (环绕通知):编程式控制目标对象的方法调用。环绕通知是所有通知类型中可操作范围最大的一种,因为它可以直接拿到目标对象,以及要执行的方法,所以环绕通知可以任意的在目标对象的方法调用前后搞事,甚至不调用目标对象的方法

执行目标方法

在执行完Before或Around通知的前置逻辑后,代理对象会调用目标对象的实际方法。目标方法执行完成后,代理对象会继续执行After、Around的后置逻辑、AfterReturning或AfterThrowing通知。

返回结果或抛出异常

代理对象在完成所有通知逻辑后,将目标方法的返回结果返回给调用方。如果目标方法抛出异常,代理对象也会处理异常并根据配置决定是否重新抛出或转换异常。

结束

AOP的执行流程在代理对象返回结果或抛出异常后结束,整个过程是透明的,调用方无需关心代理的存在,目标对象的行为在运行时被增强。

AOP的使用方法

1.引入依赖

在 Maven 项目中,引入 Spring AOP 依赖:

<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-aopartifactId>
dependency>

2.定义切面类

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class LoggingAspect {

    @Around("execution(* com.example.service..*(..))")
    public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
        // 获取方法名
        String methodName = joinPoint.getSignature().toShortString();
        System.out.println("Method " + methodName + " execution started.");
        
        long start = System.currentTimeMillis();
        // 执行目标方法
        Object result = joinPoint.proceed();
        long executionTime = System.currentTimeMillis() - start;

        System.out.println("Method " + methodName + " execution finished. Time taken: " + executionTime + "ms.");
        return result;
    }
}

3.示例业务代码

package com.example.service;

import org.springframework.stereotype.Service;

@Service
public class MyService {

    public void performTask() {
        System.out.println("Executing business logic in performTask...");
    }

    public void anotherTask() {
        System.out.println("Executing another business logic...");
    }
}

4. 启动应用

Spring Boot 会自动扫描到 @Aspect 注解的类,并生效。

5.运行结果

Method MyService.performTask execution started.
Executing business logic in performTask...
Method MyService.performTask execution finished. Time taken: 5ms.

Method MyService.anotherTask execution started.
Executing another business logic...
Method MyService.anotherTask execution finished. Time taken: 3ms.

AOP的应用场景有哪些?

  • 日志记录:自定义日志记录注解,利用 AOP,一行代码即可实现日志记录。
  • 性能统计:利用 AOP 在目标方法的执行前后统计方法的执行时间,方便优化和分析。
  • 事务管理:@Transactional 注解可以让 Spring 为我们进行事务管理比如回滚异常操作,免去了重复的事务管理逻辑。@Transactional注解就是基于 AOP 实现的。
  • 权限控制:利用 AOP 在目标方法执行前判断用户是否具备所需要的权限,如果具备,就执行目标方法,否则就不执行。例如,SpringSecurity 利用@PreAuthorize 注解一行代码即可自定义权限校验。
  • 接口限流:利用 AOP 在目标方法执行前通过具体的限流算法和实现对请求进行限流处理。
  • 缓存管理:利用 AOP 在目标方法执行前后进行缓存的读取和更新。

AOP实现方式有哪些?

AOP 的常见实现方式有动态代理、字节码操作等方式。
Spring AOP 就是基于动态代理的,如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 JDK Proxy,去创建代理对象,而对于没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用 Cglib 生成一个被代理对象的子类来作为代理,如下图所示:
躺着敲代码第二天-----聊聊Spring的AOP(面向切面编程)_第3张图片

当然你也可以使用 AspectJ !Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。
Spring AOP 属于运行时增强,而 AspectJ 是编译时增强。 Spring AOP 基于代理(Proxying),而 AspectJ 基于字节码操作(Bytecode Manipulation)。
Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。AspectJ 相比于 Spring AOP 功能更加强大,但是 Spring AOP 相对来说更简单,
如果我们的切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择 AspectJ ,它比 Spring AOP 快很多。

参考来源:
什么是AOP-----JavaGuide(Guide哥)
Spring AOP原理分析!-----猿java

你可能感兴趣的:(spring,java)