快速上手Spring AOP

简介: 本文是Spring AOP系列教程的应用篇,深入浅出地讲解了AOP核心概念与实战技巧。通过代码示例,演示如何利用切面实现日志记录、性能监控等横切关注点的统一管理,提升代码复用性与可维护性,助你快速掌握Spring AOP应用精髓。
  1. 前言
    哈喽大家好吖~欢迎来到Spring AOP系列教程的上篇 - 应用篇。在本篇,我们将专注于Spring AOP的实际应用,通过具体的代码示例和场景分析,帮助大家掌握AOP的使用方法和技巧。而在后续的下篇中,我们将深入探讨Spring AOP的实现原理和底层机制。

AOP(Aspect-Oriented Programming,面向切面编程)是Spring框架中的核心特性之一,它能够帮助我们解决横切关注点(如日志记录、性能统计、安全控制、事务管理等)的问题,提高代码的模块化程度和复用性。

插播一条消息~
🔍十年经验淬炼 · 系统化AI学习平台推荐\
系统化学习AI平台https://www.captainbed.cn/scy/

📚 完整知识体系: 从数学基础 → 工业级项目(人脸识别/自动驾驶/GANs),内容由浅入深
💻 实战为王: 每小节配套可运行代码案例(提供完整源码)
🎯 零基础友好: 用生活案例讲解算法,无需担心数学/编程基础
🚀 特别适合

想系统补强AI知识的开发者
转型人工智能领域的从业者
需要项目经验的学生

  1. 正文
    2.1 AOP概述
    什么是AOP?
    AOP是一种编程范式,它通过横切的方式将那些影响多个类的公共行为封装到可重用的模块中,这个模块被称为切面(Aspect) 。AOP的核心思想是将业务逻辑与横切关注点分离,从而提高代码的模块化程度和可维护性。

什么是Spring AOP?
Spring AOP是Spring框架提供的AOP实现,它基于动态代理和字节码生成技术,无需特殊的编译器支持,即可在运行时为目标对象创建代理对象,实现切面的织入。

Spring AOP主要用于:

提供声明式服务(如事务管理)
允许用户实现自定义切面
简化AOP编程
2.2 快速入门
接下来,让我们通过一个实际案例来快速体验Spring AOP的使用。我们将实现一个简单的功能:记录各个接口方法的执行时间。

2.2.1 引入AOP依赖
首先,我们需要在项目中引入Spring AOP的依赖。如果使用Maven,可以在pom.xml中添加以下依赖:


org.springframework.boot
spring-boot-starter-aop

2.2.2 编写AOP程序
接下来,我们编写一个AOP切面类,用于记录方法执行时间:

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

/**

  • 方法执行时间记录切面
    */
    @Aspect // 标识这是一个切面类
    @Component // 交给Spring容器管理
    public class MethodExecutionTimeAspect {

    private static final Logger logger = LoggerFactory.getLogger(MethodExecutionTimeAspect.class);

    /**

    • 环绕通知,用于记录方法执行时间
    • @param joinPoint 连接点对象
    • @return 方法执行结果
    • @throws Throwable 方法执行过程中可能抛出的异常
      /
      @Around("execution(
      com.example.demo.controller..(..))") // 切点表达式,匹配controller包下的所有方法
      public Object recordExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
      // 记录开始时间
      long startTime = System.currentTimeMillis();

      try {

       // 执行目标方法
       Object result = joinPoint.proceed();
      
       // 记录结束时间并计算执行时间
       long endTime = System.currentTimeMillis();
       long executionTime = endTime - startTime;
      
       // 记录日志
       logger.info("方法 [{}] 执行时间: {} ms", 
                  joinPoint.getSignature().toShortString(), 
                  executionTime);
      
       return result;
      

      } catch (Throwable e) {

       // 记录异常信息
       long endTime = System.currentTimeMillis();
       long executionTime = endTime - startTime;
       logger.error("方法 [{}] 执行异常,执行时间: {} ms,异常信息: {}",
                   joinPoint.getSignature().toShortString(),
                   executionTime,
                   e.getMessage());
       throw e;
      

      }
      }
      }

通过以上代码以及讲解,感受到面向切面编程的优势

通过上面的例子,我们可以看到面向切面编程的几个显著优势:

代码解耦:将日志记录、性能监控等横切关注点与业务逻辑分离
代码复用:一个切面可以应用到多个目标对象和方法
集中维护:横切关注点的代码集中在切面中,便于维护和修改
声明式编程:通过注解即可完成切面的织入,无需修改目标代码
2.3 Spring AOP 详解
2.3.1 核心概念
Spring AOP中有几个核心概念需要理解:

概念 描述
切点(Pointcut) 定义了哪些连接点会被切入,通常使用表达式来描述
连接点(Join Point) 程序执行过程中的某个特定点,如方法调用、异常抛出等
通知(Advice) 切面在特定连接点执行的动作,如前置通知、后置通知等
切面(Aspect) 切点和通知的组合,是一个横切关注点的模块化
下面我们详细解释这些概念:

2.3.1.1 切点(Pointcut)
切点是指我们要对哪些连接点进行拦截的定义。在Spring AOP中,切点通常使用切点表达式来描述,它决定了哪些方法会被切入。

2.3.1.2 连接点(Join Point)
连接点是程序执行过程中的一个点,例如方法的调用或异常的抛出。在Spring AOP中,连接点通常指的是方法的执行。

2.3.1.3 通知(Advice)
通知是切面在特定连接点执行的动作。Spring AOP提供了多种类型的通知,用于在不同时机执行切面逻辑。

2.3.1.4 切面(Aspect)
切面是切点和通知的组合,它封装了横切关注点的实现。在Spring中,切面通常是一个被@Aspect注解标记的类。

2.3.2 通知类型
Spring AOP提供了五种类型的通知:

前置通知(@Before) :在目标方法执行之前执行
后置通知(@After) :在目标方法执行之后执行,无论方法是否正常返回
返回通知(@AfterReturning) :在目标方法正常返回之后执行
异常通知(@AfterThrowing) :在目标方法抛出异常时执行
环绕通知(@Around) :围绕目标方法执行,可以在方法执行前后自定义逻辑
下面是一个包含这五种通知类型的示例代码:

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

@Aspect
@Component
public class AllAdviceTypesAspect {

private static final Logger logger = LoggerFactory.getLogger(AllAdviceTypesAspect.class);

// 定义切点
@Pointcut("execution(* com.example.demo.service.*.*(..))")
public void serviceMethods() {}

// 前置通知
@Before("serviceMethods()")
public void beforeAdvice(JoinPoint joinPoint) {
    logger.info("前置通知:方法 [{}] 即将执行", joinPoint.getSignature().toShortString());
}

// 后置通知
@After("serviceMethods()")
public void afterAdvice(JoinPoint joinPoint) {
    logger.info("后置通知:方法 [{}] 执行结束", joinPoint.getSignature().toShortString());
}

// 返回通知
@AfterReturning(pointcut = "serviceMethods()", returning = "result")
public void afterReturningAdvice(JoinPoint joinPoint, Object result) {
    logger.info("返回通知:方法 [{}] 正常返回,返回结果:{}", 
               joinPoint.getSignature().toShortString(), result);
}

// 异常通知
@AfterThrowing(pointcut = "serviceMethods()", throwing = "ex")
public void afterThrowingAdvice(JoinPoint joinPoint, Exception ex) {
    logger.error("异常通知:方法 [{}] 抛出异常,异常信息:{}", 
                joinPoint.getSignature().toShortString(), ex.getMessage());
}

// 环绕通知
@Around("serviceMethods()")
public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
    logger.info("环绕通知开始:方法 [{}] 准备执行", joinPoint.getSignature().toShortString());

    long startTime = System.currentTimeMillis();
    Object result = null;

    try {
        // 执行目标方法
        result = joinPoint.proceed();
        long endTime = System.currentTimeMillis();
        logger.info("环绕通知正常结束:方法 [{}] 执行成功,耗时:{} ms", 
                   joinPoint.getSignature().toShortString(), endTime - startTime);
    } catch (Exception e) {
        long endTime = System.currentTimeMillis();
        logger.error("环绕通知异常结束:方法 [{}] 执行异常,耗时:{} ms,异常信息:{}", 
                    joinPoint.getSignature().toShortString(), endTime - startTime, e.getMessage());
        throw e;
    }

    return result;
}

}

接下来,我们测试两种情况:

情况1:正常运行的流程

当目标方法正常执行时,通知的执行顺序为:

环绕通知开始
前置通知
目标方法执行
环绕通知正常结束
返回通知
后置通知
情况2:程序抛出异常的情况

当目标方法抛出异常时,通知的执行顺序为:

环绕通知开始
前置通知
目标方法执行(抛出异常)
环绕通知异常结束
异常通知
后置通知
2.3.3 @PointCut(公共切点)
当多个通知需要使用相同的切点表达式时,可以使用@Pointcut注解定义一个公共切点,提高代码的复用性和可维护性。

@Aspect
@Component
public class CommonPointcutAspect {

// 定义公共切点
@Pointcut("execution(* com.example.demo.service.*.*(..))")
public void serviceLayerPointcut() {}

// 使用公共切点
@Before("serviceLayerPointcut()")
public void beforeServiceMethod(JoinPoint joinPoint) {
    logger.info("Service方法 [{}] 即将执行", joinPoint.getSignature().toShortString());
}

// 使用公共切点
@AfterReturning("serviceLayerPointcut()")
public void afterReturningServiceMethod(JoinPoint joinPoint) {
    logger.info("Service方法 [{}] 执行成功", joinPoint.getSignature().toShortString());
}

}

2.3.4 切面优先级@Order
当多个切面应用到同一个目标方法时,可以使用@Order注解指定切面的执行顺序。@Order注解的值越小,切面的优先级越高。

// 第一个切面
@Aspect
@Component
@Order(1) // 优先级高
public class FirstAspect {
@Before("execution( com.example.demo.controller..*(..))")
public void beforeAdvice() {
logger.info("FirstAspect - 前置通知");
}
}

// 第二个切面
@Aspect
@Component
@Order(2) // 优先级低
public class SecondAspect {
@Before("execution( com.example.demo.controller..*(..))")
public void beforeAdvice() {
logger.info("SecondAspect - 前置通知");
}
}

执行结果:

FirstAspect - 前置通知
SecondAspect - 前置通知

可以看到,Order值小的切面先执行。

2.3.5 切点表达式
Spring AOP提供了多种切点表达式,用于定义哪些方法会被切入。常用的有execution表达式和@annotation表达式。

2.3.5.1 execution表达式
execution表达式是最常用的切点表达式,它用于匹配方法的执行。其基本语法如下:

execution([修饰符] 返回值类型 包名.类名.方法名(参数类型) [throws 异常])

其中,部分可以使用通配符:

*:匹配任意字符,但只能匹配一个元素
..:匹配任意字符,可以匹配多个元素,在包名中表示任意子包,在参数中表示任意参数
+:匹配指定类及其子类
示例:

// 匹配com.example.demo.service包下所有类的所有方法
execution( com.example.demo.service..*(..))

// 匹配com.example.demo.controller包及其子包下所有类的public方法
execution(public com.example.demo.controller...*(..))

// 匹配所有以"get"开头的方法
execution( get(..))

// 匹配参数为String类型的方法
execution( (String))

2.3.5.2 @annotation表达式
@annotation表达式用于匹配被指定注解标记的方法。使用步骤如下:

1.编写自定义注解:

import java.lang.annotation.*;

@Target(ElementType.METHOD) // 只能应用于方法
@Retention(RetentionPolicy.RUNTIME) // 运行时保留
public @interface Loggable {
// 注解属性,可以用来传递参数
String value() default "";
}

2.使用@annotation表达式来描述切点:

@Aspect
@Component
public class AnnotationPointcutAspect {

// 匹配被@Loggable注解标记的方法
@Before("@annotation(loggable)")
public void logMethod(JoinPoint joinPoint, Loggable loggable) {
    logger.info("方法 [{}] 被@Loggable注解标记,注解值:{}",
               joinPoint.getSignature().toShortString(),
               loggable.value());
}

}

3.在连接点的方法上添加自定义注解:

当getUser方法被调用时,AnnotationPointcutAspect中的logMethod通知会被触发。

相关文章
|
6月前
|
人工智能 监控 安全
如何快速上手【Spring AOP】?核心应用实战(上篇)
哈喽大家好吖~欢迎来到Spring AOP系列教程的上篇 - 应用篇。在本篇,我们将专注于Spring AOP的实际应用,通过具体的代码示例和场景分析,帮助大家掌握AOP的使用方法和技巧。而在后续的下篇中,我们将深入探讨Spring AOP的实现原理和底层机制。 AOP(Aspect-Oriented Programming,面向切面编程)是Spring框架中的核心特性之一,它能够帮助我们解决横切关注点(如日志记录、性能统计、安全控制、事务管理等)的问题,提高代码的模块化程度和复用性。
|
4月前
|
XML Java 应用服务中间件
【SpringBoot(一)】Spring的认知、容器功能讲解与自动装配原理的入门,带你熟悉Springboot中基本的注解使用
SpringBoot专栏开篇第一章,讲述认识SpringBoot、Bean容器功能的讲解、自动装配原理的入门,还有其他常用的Springboot注解!如果想要了解SpringBoot,那么就进来看看吧!
564 2
|
19天前
|
数据采集 存储 人工智能
RAG实战指南:如何让大模型“记得住、答得准、学得快”?
AI博主maoku详解RAG技术:为大模型配备“外接大脑”,解决知识滞后、幻觉编造、专业适配不足三大痛点。文章系统讲解RAG原理、三大开发模式选择、Embedding模型选型、完整实战代码及效果评估,助你快速构建靠谱、可溯源、实时更新的智能问答系统。
|
缓存 druid Java
SpringBoot源码 | prepareContext方法解析
本文主要讲述SpringBoot启动流程源码中的prepareContext()方法
SpringBoot源码 | prepareContext方法解析
|
存储 安全 Java
ConcurrentHashMap底层实现原理
ConcurrentHashMap底层实现原理
741 0
|
8天前
|
人工智能 自然语言处理 安全
云上部署OpenClaw(Clawdbot)多少钱?2026年阿里云部署OpenClaw新手教程及收费标准参考
OpenClaw(前身为Clawdbot、Moltbot)作为一款开源AI代理与自动化平台,凭借自然语言控制、多工具集成、跨场景任务执行等核心优势,成为个人办公效率提升与轻量团队协作优化的重要工具。其不仅能实现文件处理、邮件管理、代码生成等基础操作,还可通过对接主流大语言模型构建个性化工作流,适配从个人日常办公到团队协同的多元需求。2026年阿里云推出的一键部署方案,通过预置专属镜像简化了环境配置流程,同时提供清晰透明的计费模式,让不同需求的用户都能精准规划成本并快速落地使用。本文将详细拆解阿里云部署OpenClaw的完整流程,结合官方计费标准梳理费用构成与成本控制方案,为用户提供从部署到运维
363 3
|
8月前
|
Java 测试技术 BI
说一说 Maven 常用的命令
我是小假 期待与你的下一次相遇 ~
174 2
|
Java 数据库连接 数据库
Spring基础3——AOP,事务管理
AOP简介、入门案例、工作流程、切入点表达式、环绕通知、通知获取参数或返回值或异常、事务管理
Spring基础3——AOP,事务管理
|
存储 监控 Java
SpringCloud极简入门-服务注册与发现-Eureka
微服务的其中一个特点是服务之间需要进行网络通信,服务器之间发起调用时调用服务得知道被调用服务的通信地址,试问当微服务数量成百上千之多,程序员该如何管理众多的服务通信地址,对于随时新增加的微服务和下线的微服务,又应该如何去动态添加和删除这些微服务的通信地址呢?所以手工管理服务的通信地址是一件遥不可及的事情,我们需要借助一个强大的工具帮我们实现这一功能 - Eureka,同类型的组件还有 zookeeper,consul等
332 0
SpringCloud极简入门-服务注册与发现-Eureka
|
存储 开发框架 安全
ASP.NET WebApi 如何使用 OAuth2.0 认证
ASP.NET WebApi 如何使用 OAuth2.0 认证
396 1