目录
1. 前言
哈喽大家好吖~欢迎来到Spring AOP系列教程的上篇 - 应用篇。在本篇,我们将专注于Spring AOP的实际应用,通过具体的代码示例和场景分析,帮助大家掌握AOP的使用方法和技巧。而在后续的下篇中,我们将深入探讨Spring AOP的实现原理和底层机制。
AOP(Aspect-Oriented Programming,面向切面编程)是Spring框架中的核心特性之一,它能够帮助我们解决横切关注点(如日志记录、性能统计、安全控制、事务管理等)的问题,提高代码的模块化程度和复用性。
插播一条消息~
🔍十年经验淬炼 · 系统化AI学习平台推荐
系统化学习AI平台https://www.captainbed.cn/scy/
- 📚 完整知识体系:从数学基础 → 工业级项目(人脸识别/自动驾驶/GANs),内容由浅入深
- 💻 实战为王:每小节配套可运行代码案例(提供完整源码)
- 🎯 零基础友好:用生活案例讲解算法,无需担心数学/编程基础
🚀 特别适合
- 想系统补强AI知识的开发者
- 转型人工智能领域的从业者
- 需要项目经验的学生
2. 正文
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中添加以下依赖:
<!-- Spring AOP -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
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通知会被触发。
3. 小结
本文我们详细介绍了Spring AOP的应用,包括:
AOP基础概念与Spring AOP框架特点解析
基于实际案例的Spring AOP快速入门指南
五大通知类型详解及适用场景对比
高效切点管理:@Pointcut用法详解
多切面优先级控制@Order实战技巧
两种核心切点表达式:execution与@annotation深度剖析
通过本文的学习,相信大家已经掌握了Spring AOP的基本使用方法。AOP作为Spring框架的核心特性之一,在实际项目中有广泛的应用,如日志记录、性能监控、事务管理、安全控制等。合理使用AOP可以有效提高代码的模块化程度和可维护性。
在下一篇文章中,我们将深入探讨Spring AOP的实现原理,敬请期待!如果文章对你有帮助的话,不要忘了点赞关注,谢谢支持喔~