Spring AOP原理解析

 更新时间:2026年02月24日 15:04:44   作者:马尔代夫哈哈哈  
AOP面向切片编程思想,是一种对一类问题集中处理的思想,Spring AOP就是为了解决这些问题存在,是AOP思想的其中一种实现方式,下面给大家详细介绍Spring AOP原理解析,感兴趣的朋友跟随小编一起看看吧

1.什么是Spring AOP?

AOP=>面向切片编程思想,是一种对一类问题集中处理的思想,比如拦截器,统一返回结果管理,统一异常处理,登录校验......如果使用OOP(面向结果编程)会让相同的代码重复多次出现,业务方法中混杂着非核心的逻辑。

Spring AOP就是为了解决这些问题存在,是AOP思想的其中一种实现方式

2.SpringAOP优点与上手

优点:

  • 不影响原有代码,解耦
  • 便于维护功能
  • 提高开发效率
  • 减少重复代码

快速上手SpringAOP

编写一个使用SpringAOP计算所有方法的运行时长的例子

1.在pom.xml文件引入依赖

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

注意:SpringBoot使用版本号为4.x.x, AOP依赖需要添加版本号

2.编写AOP程序

package com.example.springbookdemo.aspect;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Slf4j
@Aspect
@Component
public class TimeRecordAspect {
 @Around("execution(* com.example.springbookdemo.controller.*.*(..))")//该注解()中是要执行方法的路径
    //参数部分的ProceedingJoinPoint joinPoint即要执行的方法
    //不确定执行方法的返回类型,因此在这里设返回类型为Object
    public Object timeRecord(ProceedingJoinPoint joinPoint) throws Throwable {
        long start=System.currentTimeMillis();//记录方法执行前时间
        //执行方法
        Object proceed = joinPoint.proceed();//proceed()方法返回类型固定为Object
        long end=System.currentTimeMillis();//记录方法执行完的时间
        log.info(joinPoint.getSignature()+"方法耗时:"+(end-start)+"ms");//getSignature()用于获取执行方法名
        return proceed;
    }
}

注解解释:

  • Aspect=>标识切面类
  • Component=>将该类交给Spring容器管理,使其能自动扫描并注入
  • Around=>指明对哪些方法生效
  • Slf4j=>自动生成日志对象,可调用log.info()等方法打印日志

Spring AOP 的核心术语

理解这些术语是掌握 AOP 的关键:

  • Aspect(切面)
    • 是什么:横切关注点的模块化。它是一个,里面包含了需要被织入到业务代码中的各种通知(Advice)。包含了切点,通知,连接点
  • Join Point(连接点)
    • 是什么:程序执行过程中可以插入切面的点。在 Spring AOP 中,连接点总是代表执行的方法
  • Pointcut(切点)
    • 是什么:一个表达式,用于定义在哪些连接点上应用通知。它告诉 AOP:“在哪里” 执行切面代码。例如,你可以定义一个切点,匹配所有 com.example.service包下以 delete开头的方法。
  • Advice(通知)
    • 是什么:切面在特定连接点上执行的动作。指具体要做的⼯作, 指哪些重复的逻辑,它回答了 “什么时候” 和 “做什么” 的问题。
  • 类型
    • @Before:在目标方法执行之前执行。
    • @After:在目标方法执行之后执行(无论是否成功完成)。
    • @AfterReturning:在目标方法成功执行并返回结果后执行。
    • @AfterThrowing:在目标方法抛出异常后执行。
    • @Around最强大的通知,它包围了连接点。可以在方法调用前后执行自定义行为,并决定是否继续执行方法、返回值或抛出异常。

用上面的例子来区分

3.通知类型注解

写两个方法,用于测试

package com.example.springaopdemo.controller;
@Slf4j
@RestController
@RequestMapping("/test")
public class TestController {
    @RequestMapping("/t1")
    public String t1(){
        log.info("执行t1");
        return "t1";
    }
    @RequestMapping("/t2")
    public boolean t2(){
        log.info("执行t2");
        return true;
    }
}

编写AOP

package com.example.springaopdemo.aspect;
@Aspect
@Component
@Slf4j
public class TestAspect {
    //抽取切点
    @Pointcut("execution(* com.example.springaopdemo.controller.*.*(..))")
    public void pt(){}
    //前置通知
    @Before("pt()")
    public void doBefore() {
        log.info("执行doBefore方法");
    }
    //后置通知
    @After("pt()")
    public void doAfter() {
        log.info("执行 After 方法");
    }
    //返回后通知
    @AfterReturning("pt()")
    public void doAfterReturning() {
        log.info("执行 AfterReturning 方法");
    }
    //抛出异常后通知
    @AfterThrowing("pt()")
    public void doAfterThrowing() {
        log.info("执行 doAfterThrowing 方法");
    }
    //添加环绕通知
    @Around("pt()")
    public Object doAround(ProceedingJoinPoint joinPoint)  {
        log.info("Around 方法开始执行");
        Object result = null;
        try {
            result = joinPoint.proceed();
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
        log.info("Around 方法结束执行");
        return result;
    }
}

结果展示:

访问t1=>即接口正常访问

访问t2=>即接口发生异常且不捕获

访问t2=>即接口发生异常且捕获不throw

4.@PointCut+@Order

@PointCut

在上面中存在大量的同样的切点表达式,使用该注解能够定义可重用的切点表达式,避免在多个通知中重复编写相同的表达式。

通过@PointCut注解定义一个公共切点方法,该方法本身没有实现,仅作为切点表达式的载体:

@PointCut("execution(* com.example.service.*.*(..))")
public void serviceLayer() {}

重用切点表达式

定义好的切点可以在其他通知中通过方法名引用,避免重复编写表达式:

@Before("serviceLayer()")
public void beforeService() {
    // 前置通知逻辑
}
@After("serviceLayer()")
public void afterService() {
    // 后置通知逻辑
}

@Order用于设置优先级,控制多个切面的执行顺序,@Order(),()中的数字越小,优先级越高

@Component
@Aspect
@Slf4j
public class TestAspect2 {
    @Before("com.example.springaopdemo.aspect.TestAspect.pt()")
    public void doBefore() {
        log.info("执行doBefore2方法");
    }
}
@Component
@Aspect
@Slf4j
public class TestAspect3 {
    @Before("com.example.springaopdemo.aspect.TestAspect.pt()")
    public void doBefore() {
        log.info("执行doBefore3方法");
    }
}
@Aspect
@Component
@Slf4j
public class TestAspect {
    //抽取切点
    @Pointcut("execution(* com.example.springaopdemo.controller.*.*(..))")
    public void pt(){}
    //前置通知
    @Before("pt()")
    public void doBefore() {
        log.info("执行doBefore方法");
    }
}

执行结果:也就是切面默认执行顺序(按照类名排序)

通过添加@Order注解可以人为控制切面执行顺序

@Order(3)

public class TestAspect(){}

@Order(2)

public class TestAspect2(){}

@Order(1)

public class TestAspect3(){}

添加之后执行结果:

5.切点表达式

有两种

第一种execution(<访问修饰符><返回类型><包名.类名.方法名(方法参数)><异常>)

其中访问修饰符和异常可以省略," * "匹配任意字符," .. "匹配多个连续符号

第二种@annotation=>自定义注解

相比较第一种有一定规律来说,想要执行的方法没有规律可以使用这种

//自定义注解
@Retention(RetentionPolicy.RUNTIME)//设置生命周期
@Target({ElementType.METHOD})//设置注解使用位置
public @interface TimeRecord {
}
//注解通过切面实现
@Aspect
@Component
@Slf4j
public class TimeRecordAspect {
    @Around("@annotation(com.example.springaopdemo.aspect.TimeRecord)")
    public  Object timeRecord(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        Object result = joinPoint.proceed();
        long end = System.currentTimeMillis();
        log.info(joinPoint.getSignature() + "耗时" + (end - start) + "ms");
        return result;
    }
}

注解完成自定义之后,只要在对应方法添加该注解即可实现计算方法运行时间的功能

6.代理模式

它为另一个对象提供一个替身或占位符,以控制对这个对象的访问。其核心目的是:在不修改原始对象(目标对象)代码的前提下,通过代理对象来增强或控制对目标对象的访问

一个标准的代理模式通常包含三个角色:

  • 抽象主题 (Subject): 定义了目标对象和代理对象的公共接口。这样在任何使用目标对象的地方都可以透明地使用代理对象。
  • 真实主题 (Real Subject): 即“目标对象”,是业务逻辑的真正执行者。
  • 代理 (Proxy): 持有对真实主题的引用,客户端直接与代理交互。代理可以在调用真实主题的前后,添加额外的处理逻辑。

分为静态代理和动态代理,区别在于代理对象的class文件生成的时机

静态代理:程序运行之前就创建好了代理对象

动态代理:程序运行时动态生成代理对象

其中动态代理有两种

  • jdk动态代理=>要求目标类必须实现至少一个接口。代理对象会实现与目标类相同的接口。
  • cGlib字节码增强=>当目标类没有实现接口时,Spring会使用CGLIB。它通过生成目标类的子类来创建代理,因此不能代理 final类或方法。

7.Spring AOP原理

Spring AOP是通过动态代理将横切关注点(如日志,事务)模块化,并织入到目标方法的编程范式

它的实现同时使用了jdk和CGlib

工作流程

  1. Spring容器启动时,会自动扫描被@Aspect注解的类
  2. 通过扫描切点表达式,找到所有匹配的Bean(即目标对象)
  3. 通过ProxyFactory创建目标对象对应的代理对象(jdk/cglib)并将通知封装成拦截器链
  4. 在调用代理对象方法时,拦截器链被触发,按顺序执行通知逻辑,最终调用目标方法

此外ProxyFactory有个很重要的属性proxyTargetClass,

SpringBoot默认为true,Spring默认为false

可以通过在application.properties中进行配置:spring.aop.proxy-target-class=true/false

到此这篇关于Spring AOP原理解析的文章就介绍到这了,更多相关Spring AOP原理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • idea前后跳转箭头的快捷键

    idea前后跳转箭头的快捷键

    这篇文章主要介绍了idea前后跳转箭头的快捷键,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-02-02
  • 深入解析Spring中的立即加载和延迟加载

    深入解析Spring中的立即加载和延迟加载

    本文介绍了Spring框架中的立即加载和延迟加载策略,并解释了它们的适用场景和使用方法,根据具体的需求和应用场景,选择合适的加载策略,可以提高应用程序的性能和资源利用效率,感兴趣的朋友跟随小编一起看看吧
    2023-07-07
  • springboot中的pom文件 project报错问题

    springboot中的pom文件 project报错问题

    这篇文章主要介绍了springboot中的pom文件 project报错问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-01-01
  • Java主键生成之@Id和@GeneratedValue使用详解

    Java主键生成之@Id和@GeneratedValue使用详解

    这篇文章主要介绍了Java主键生成之@Id和@GeneratedValue的使用,@Id和@GeneratedValue注解就是JPA中用于定义主键和主键生成策略的关键注解,理解这两个注解的使用和不同的主键生成策略,对于开发高效、稳定的数据持久化应用至关重要,需要的朋友可以参考下
    2025-05-05
  • Deep Module深模块之软件设计

    Deep Module深模块之软件设计

    这篇文章主要介绍了软件设计之Deep Module深模块详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-07-07
  • Spring security实现对账户进行加密

    Spring security实现对账户进行加密

    这篇文章主要介绍了Spring security实现对账户进行加密,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-03-03
  • Java并发教程之volatile关键字详解

    Java并发教程之volatile关键字详解

    这篇文章主要给大家介绍了关于Java并发教程之volatile关键字的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用Java具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2019-11-11
  • Java Optional<Foo>转换成List<Bar>的实例方法

    Java Optional<Foo>转换成List<Bar>的实例方法

    在本篇内容里小编给大家整理的是一篇关于Java Optional<Foo>转换成List<Bar>的实例方法,有需要的朋友们可以跟着学习下。
    2021-06-06
  • springboot简单接入websocket的操作方法

    springboot简单接入websocket的操作方法

    这篇文章主要介绍了springboot简单接入websocket的方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-05-05
  • Java使用net.lingala.zip4j导出压缩包文件的详细步骤

    Java使用net.lingala.zip4j导出压缩包文件的详细步骤

    本文介绍了如何使用zip4j1.x版本实现加密压缩指定文件夹并删除原文件夹的功能,解决了AES-256加密权限问题、中文乱码和文件夹删除失败等核心问题,代码兼容旧版依赖,核心配置清晰,并附带了详细的操作说明,需要的朋友可以参考下
    2026-01-01

最新评论