0%

Spring AOP

Spring AOP 深度解析:从原理到实战

Spring AOP(Aspect-Oriented Programming,面向切面编程)是 Spring 框架的核心特性之一,旨在解决横切关注点(如日志、事务、权限)在代码中分散、冗余的问题。它通过动态代理技术,将横切逻辑与业务逻辑解耦,实现 “一次定义,多处复用”。从 “核心价值→概念体系→动态代理→实战配置→避坑指南” 五个维度,彻底拆解 Spring AOP 的底层机制与使用方法。

为什么需要 Spring AOP?—— 解决横切关注点的痛点

在传统开发中,日志、事务、异常处理等横切关注点(跨越多个模块的通用逻辑)会嵌入到业务代码中,导致三大核心问题:

问题 具体表现 示例场景
代码冗余 相同的日志 / 事务逻辑重复出现在数十个方法中,修改时需逐一修改 每个 Service 方法都要写 log.info("方法开始执行")
代码混乱 业务逻辑与横切逻辑混杂,难以区分核心功能与辅助功能 一个 createOrder() 方法中,订单业务、日志、事务代码交织
维护困难 横切逻辑变更时,需修改所有相关业务类,风险高、效率低 日志格式从 “INFO” 改为 “DEBUG”,需修改所有日志语句

AOP 的解决方案:将横切逻辑横向抽取为 “切面(Aspect)”,通过动态代理技术,在不修改业务代码的前提下,将切面 “织入” 到目标方法的指定位置(如方法执行前、执行后),实现与业务逻辑的解耦。

Spring AOP 核心概念:理解 AOP 的 “语言体系”

AOP 有一套标准化的概念体系,是理解和使用 AOP 的基础。用 “日志切面” 案例对应解释每个概念:

概念 官方定义 日志切面案例对应
横切关注点 从业务逻辑中抽取的、跨越多个模块的通用逻辑(如日志、事务) 所有方法的 “执行日志记录” 逻辑
目标对象(Target) 被切面织入的对象(即业务逻辑对象) OrderServiceUserService 等业务类实例
代理对象(Proxy) 目标对象被织入切面后,Spring 生成的代理对象(包含目标方法 + 增强逻辑) OrderService 的代理对象,调用 createOrder() 时会先记录日志
连接点(Joinpoint) 程序执行过程中可插入切面的 “点”(Spring AOP 中仅支持方法执行 OrderService.createOrder()UserService.getUser() 等方法
切点(Pointcut) 对连接点的 “筛选规则”,定义哪些方法会被织入切面(即 “要增强哪些方法”) “所有 Service 类的 public 方法”
通知(Advice) 切面的具体增强逻辑(即 “要做什么”),按执行时机分为 5 种类型 “方法执行前记录日志”“方法异常时记录错误日志”
织入(Weaving) 将通知(Advice)应用到目标对象(Target)的过程(Spring 中是运行期织入 动态代理生成时,将日志逻辑嵌入 createOrder() 方法的执行流程
切面(Aspect) 切点(Pointcut)+ 通知(Advice)的组合(即 “对哪些方法,做什么增强”) “对所有 Service 的 public 方法,执行前记录日志”
通知器(Advisor) 简化版切面,仅包含 “一个切点 + 一个通知”(Spring 早期接口式 AOP 常用) “对 createOrder() 方法,执行后记录事务状态”

Spring AOP 的动态代理:AOP 的 “实现引擎”

Spring AOP 不修改目标类的字节码,而是在运行期通过动态代理生成代理对象,将切面逻辑织入。核心支持两种代理方式,由 DefaultAopProxyFactory 决定选择逻辑。

1. 代理方式的选择逻辑(源码解析)

Spring 通过 DefaultAopProxyFactory.createAopProxy() 方法选择代理方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
// 条件1:是否优化代理、是否强制使用CGLIB(proxyTargetClass=true)、是否无用户提供的接口
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class");
}
// 条件2:目标类是接口 或 已是代理类 → 使用JDK动态代理
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
// 否则 → 使用CGLIB动态代理(ObjenesisCglibAopProxy是CGLIB代理的优化实现)
return new ObjenesisCglibAopProxy(config);
} else {
// 不满足条件1 → 使用JDK动态代理
return new JdkDynamicAopProxy(config);
}
}

核心结论

  • 若目标类实现了接口 → 默认用 JDK 动态代理
  • 若目标类未实现接口 → 用 CGLIB 动态代理
  • 若配置 proxyTargetClass=true(如 @EnableAspectJAutoProxy(proxyTargetClass=true)) → 强制用 CGLIB 代理(无论是否有接口)。

2. JDK 动态代理:基于接口的代理

JDK 动态代理是 JDK 原生支持的代理方式,要求目标类必须实现接口,核心依赖 InvocationHandler 接口和 Proxy 类。

核心原理:
  1. 代理类实现目标类的所有接口;
  2. 代理类的所有方法都会委托给 InvocationHandler.invoke() 方法;
  3. invoke() 方法中,先执行切面逻辑(如日志),再调用目标对象的原方法。
简化示例(模拟 Spring JDK 代理):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// 1. 目标接口
public interface UserService {
User getUserById(Long id);
}

// 2. 目标类(实现接口)
public class UserServiceImpl implements UserService {
@Override
public User getUserById(Long id) {
return new User(id, "张三"); // 业务逻辑
}
}

// 3. 自定义InvocationHandler(织入日志切面)
public class LogInvocationHandler implements InvocationHandler {
private final Object target; // 目标对象

public LogInvocationHandler(Object target) {
this.target = target;
}

// 代理方法:所有接口方法调用都会进入这里
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 1. 前置切面逻辑:日志记录
System.out.println("方法 " + method.getName() + " 开始执行,参数:" + Arrays.toString(args));

// 2. 调用目标对象的原方法
Object result = method.invoke(target, args);

// 3. 后置切面逻辑:记录返回结果
System.out.println("方法 " + method.getName() + " 执行完成,结果:" + result);

return result;
}
}

// 4. 生成代理对象(模拟Spring操作)
public class JdkProxyDemo {
public static void main(String[] args) {
UserService target = new UserServiceImpl();
// 生成代理对象:参数(类加载器、目标接口、InvocationHandler)
UserService proxy = (UserService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new LogInvocationHandler(target)
);

// 调用代理对象方法(会触发切面逻辑)
proxy.getUserById(1L);
}
}

输出结果

1
2
方法 getUserById 开始执行,参数:[1]
方法 getUserById 执行完成,结果:User(id=1, name=张三)

3. CGLIB 动态代理:基于继承的代理

CGLIB(Code Generation Library)是一个第三方代码生成库,无需目标类实现接口,通过继承目标类生成子类作为代理类(因此目标类不能是 final,否则无法继承)。

核心原理:
  1. CGLIB 生成目标类的子类(代理类);
  2. 重写目标类的非 final 方法,在重写方法中织入切面逻辑;
  3. 调用代理类的方法时,先执行切面逻辑,再调用父类(目标类)的原方法。
简化示例(模拟 Spring CGLIB 代理):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 1. 目标类(无接口)
public class OrderService {
public void createOrder(Order order) {
System.out.println("创建订单:" + order.getId()); // 业务逻辑
}
}

// 2. 自定义MethodInterceptor(CGLIB的拦截器,类似InvocationHandler)
public class LogMethodInterceptor implements MethodInterceptor {
// 拦截方法:所有代理类的方法调用都会进入这里
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
// 1. 前置切面逻辑:日志记录
System.out.println("方法 " + method.getName() + " 开始执行,参数:" + Arrays.toString(args));

// 2. 调用目标类的原方法(通过MethodProxy避免递归调用)
Object result = methodProxy.invokeSuper(proxy, args);

// 3. 后置切面逻辑:记录执行完成
System.out.println("方法 " + method.getName() + " 执行完成");

return result;
}
}

// 3. 生成CGLIB代理对象(模拟Spring操作)
public class CglibProxyDemo {
public static void main(String[] args) {
// CGLIB增强器
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OrderService.class); // 设置父类(目标类)
enhancer.setCallback(new LogMethodInterceptor()); // 设置拦截器(切面逻辑)

// 生成代理对象(子类实例)
OrderService proxy = (OrderService) enhancer.create();

// 调用代理对象方法(触发切面逻辑)
proxy.createOrder(new Order(1L));
}
}

输出结果

1
2
3
方法 createOrder 开始执行,参数:[Order(id=1)]
创建订单:1
方法 createOrder 执行完成

4. JDK 与 CGLIB 代理的核心区别

对比维度 JDK 动态代理 CGLIB 动态代理
依赖条件 目标类必须实现接口 目标类不能是 final,方法不能是 final
代理原理 实现目标接口,委托 InvocationHandler 继承目标类,重写方法,依赖 MethodInterceptor
性能(JDK 8+) 生成代理快,执行效率高 生成代理慢,执行效率略低(但差距极小)
适用场景 目标类有接口(如 Service 接口) 目标类无接口(如工具类)

Spring AOP 实战:注解与 XML 两种配置方式

Spring AOP 支持两种主流配置方式:注解驱动(推荐,Spring Boot 常用)XML 配置(传统方式)。无论哪种方式,核心都是 “定义切面(切点 + 通知)” 并确保切面是 Spring 容器的 Bean。

1. 前置准备:引入依赖

首先需要引入 Spring AOP 和 AspectJ 相关依赖(AspectJ 是 AOP 框架,Spring AOP 借用其注解和切点表达式语法):

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- Spring AOP 核心依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.3.29.RELEASE</version>
</dependency>

<!-- Spring AspectJ 集成依赖(提供@Aspect等注解) -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>4.3.29.RELEASE</version>
</dependency>

2. 方式一:注解驱动配置(推荐)

通过 @Aspect 定义切面,@Before/@After 等注解定义通知,@EnableAspectJAutoProxy 启用自动代理。

步骤 1:启用 AspectJ 自动代理
  • XML 配置:在 spring-config.xml 中添加 <aop:aspectj-autoproxy/>

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 扫描组件(确保切面和目标类被Spring管理) -->
    <context:component-scan base-package="com.zhanghe.study.spring4.beans.aoptest"/>

    <!-- 启用AspectJ自动代理:生成代理对象,织入切面 -->
    <aop:aspectj-autoproxy/>
    </beans>
  • Java 配置(Spring Boot 常用):在配置类上添加 @EnableAspectJAutoProxy

    1
    2
    3
    4
    5
    @Configuration
    @ComponentScan(basePackages = "com.zhanghe.study.spring4.beans.aoptest")
    @EnableAspectJAutoProxy // 启用AspectJ自动代理
    public class AopConfig {
    }
步骤 2:定义切面(@Aspect)与通知

通过 @Aspect 标记切面类,@Pointcut 定义切点表达式,@Before/@AfterReturning 等定义通知:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.util.Arrays;

// 1. @Component:确保切面是Spring容器的Bean(必须)
// 2. @Aspect:标记此类为切面(必须)
// 3. @Order(1):指定切面优先级,数字越小优先级越高(多个切面时控制执行顺序)
@Component
@Aspect
@Order(1)
public class LoggingAspect {

// -------------------------- 1. 定义切点(复用切点表达式) --------------------------
// @Pointcut:定义切点,expression是切点表达式
@Pointcut("execution(public * com.zhanghe.study.spring4.beans.aoptest.*Service.*(..))")
public void servicePointcut() {} // 空方法,仅作为切点标识

// -------------------------- 2. 定义通知(增强逻辑) --------------------------
/**
* 前置通知:方法执行前执行
* @param joinPoint:连接点对象,可获取方法名、参数等信息
*/
@Before("servicePointcut()") // 引用上面定义的切点
public void beforeAdvice(JoinPoint joinPoint) {
// 获取方法名
String methodName = joinPoint.getSignature().getName();
// 获取方法参数
Object[] args = joinPoint.getArgs();
System.out.printf("【前置通知】方法 %s 开始执行,参数:%s%n", methodName, Arrays.toString(args));
}

/**
* 后置通知:方法执行后执行(无论是否异常,都会执行)
*/
@After("servicePointcut()")
public void afterAdvice(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
System.out.printf("【后置通知】方法 %s 执行结束%n", methodName);
}

/**
* 返回通知:方法正常执行完成后执行(异常时不执行)
* @param result:方法返回值,需通过returning参数指定变量名(与方法参数名一致)
*/
@AfterReturning(
pointcut = "servicePointcut()",
returning = "result" // 变量名必须与方法参数"result"一致
)
public void afterReturningAdvice(JoinPoint joinPoint, Object result) {
String methodName = joinPoint.getSignature().getName();
System.out.printf("【返回通知】方法 %s 执行成功,返回值:%s%n", methodName, result);
}

/**
* 异常通知:方法抛出异常时执行(正常时不执行)
* @param exception:方法抛出的异常,需通过throwing参数指定变量名(与方法参数名一致)
*/
@AfterThrowing(
pointcut = "servicePointcut()",
throwing = "exception" // 变量名必须与方法参数"exception"一致
)
public void afterThrowingAdvice(JoinPoint joinPoint, Exception exception) {
String methodName = joinPoint.getSignature().getName();
System.out.printf("【异常通知】方法 %s 执行异常,异常信息:%s%n", methodName, exception.getMessage());
}

/**
* 环绕通知:方法执行前后都可执行,最灵活(可控制方法是否执行、修改参数/返回值)
* @param proceedingJoinPoint:特殊的JoinPoint,可调用proceed()执行目标方法
*/
@Around("servicePointcut()")
public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
String methodName = proceedingJoinPoint.getSignature().getName();
Object[] args = proceedingJoinPoint.getArgs();

try {
// 1. 前置逻辑
System.out.printf("【环绕通知-前置】方法 %s 开始执行%n", methodName);

// 2. 执行目标方法(必须调用proceed(),否则目标方法不会执行)
Object result = proceedingJoinPoint.proceed(args);

// 3. 后置逻辑(正常执行时)
System.out.printf("【环绕通知-后置】方法 %s 执行完成%n", methodName);
return result; // 返回目标方法的结果
} catch (Exception e) {
// 4. 异常逻辑
System.out.printf("【环绕通知-异常】方法 %s 执行异常:%s%n", methodName, e.getMessage());
throw e; // 重新抛出异常,不掩盖业务异常
}
}
}
步骤 3:定义目标对象(业务类)
1
2
3
4
5
6
7
8
9
10
11
// 目标类:Service 层业务类(被切面织入)
@Component
public class UserService {
// 目标方法:会被 LoggingAspect 切面增强
public User getUserById(Long id) {
if (id == null) {
throw new IllegalArgumentException("id 不能为null"); // 模拟异常
}
return new User(id, "张三"); // 正常业务逻辑
}
}
步骤 4:测试 AOP 效果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class AopTest {
public static void main(String[] args) {
// 初始化Spring容器
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");

// 获取代理对象(注意:这里获取的是UserService的代理对象,不是原对象)
UserService userService = context.getBean(UserService.class);

// 1. 调用正常方法(无异常)
System.out.println("=== 调用正常方法 ===");
userService.getUserById(1L);

// 2. 调用异常方法(id为null)
System.out.println("\n=== 调用异常方法 ===");
try {
userService.getUserById(null);
} catch (Exception e) {
// 捕获异常,避免程序退出
}
}
}

输出结果(注意通知执行顺序)

1
2
3
4
5
6
7
8
9
10
11
12
13
=== 调用正常方法 ===
【环绕通知-前置】方法 getUserById 开始执行
【前置通知】方法 getUserById 开始执行,参数:[1]
【环绕通知-后置】方法 getUserById 执行完成
【后置通知】方法 getUserById 执行结束
【返回通知】方法 getUserById 执行成功,返回值:User(id=1, name=张三)

=== 调用异常方法 ===
【环绕通知-前置】方法 getUserById 开始执行
【前置通知】方法 getUserById 开始执行,参数:[null]
【环绕通知-异常】方法 getUserById 执行异常:id 不能为null
【后置通知】方法 getUserById 执行结束
【异常通知】方法 getUserById 执行异常,异常信息:id 不能为null

3. 方式二:XML 配置(传统方式)

通过 <aop:config> 标签定义切面、切点和通知,无需注解(适合不允许使用注解的传统项目)。

步骤 1:配置目标对象和切面的 Bean
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">

<!-- 1. 配置目标对象(业务类) -->
<bean id="userService" class="com.zhanghe.study.spring4.beans.aoptest.UserService"/>

<!-- 2. 配置切面 Bean(必须是Spring容器的Bean) -->
<bean id="loggingAspect" class="com.zhanghe.study.spring4.beans.aoptest.LoggingAspect"/>

<!-- 3. 配置AOP -->
<aop:config>
<!-- 3.1 定义切点:id是切点标识,expression是切点表达式 -->
<aop:pointcut
id="servicePointcut"
expression="execution(public * com.zhanghe.study.spring4.beans.aoptest.*Service.*(..))"/>

<!-- 3.2 定义切面:ref指向切面Bean,order指定优先级 -->
<aop:aspect ref="loggingAspect" order="1">
<!-- 3.3 配置通知:method是切面类的方法名,pointcut-ref引用切点 -->
<aop:before method="beforeAdvice" pointcut-ref="servicePointcut"/>
<aop:after method="afterAdvice" pointcut-ref="servicePointcut"/>
<aop:after-returning
method="afterReturningAdvice"
pointcut-ref="servicePointcut"
returning="result"/> <!-- 与切面方法参数名一致 -->
<aop:after-throwing
method="afterThrowingAdvice"
pointcut-ref="servicePointcut"
throwing="exception"/> <!-- 与切面方法参数名一致 -->
<aop:around method="aroundAdvice" pointcut-ref="servicePointcut"/>
</aop:aspect>
</aop:config>
</beans>
步骤 2:切面类(无注解)
1
2
3
4
5
// 切面类:无任何注解,仅包含通知方法
public class LoggingAspect {
// 通知方法与注解方式完全一致(beforeAdvice、afterAdvice等)
// ...(代码同注解方式的通知方法,省略)
}

测试效果:与注解方式完全一致,XML 配置仅改变了 “切面定义的位置”,未改变 AOP 的执行逻辑。

4. 关键:切点表达式语法

切点表达式是 AOP 的 “筛选规则”,用于定义哪些方法会被织入切面。Spring AOP 支持多种切点标识符,核心是 execution(最常用)。

(1)execution 标识符(核心)

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

  • 修饰符:可选(如 public,可省略);
  • 返回值类型:必填(* 表示任意返回值);
  • 包名。类名:必填(* 表示任意类,.. 表示子包);
  • 方法名:必填(* 表示任意方法);
  • 参数列表:必填(() 表示无参,(..) 表示任意参数,(Long) 表示单个 Long 类型参数);
  • 异常类型:可选(如 throws Exception,仅匹配抛出该异常的方法)。

常用示例

切点表达式 含义
execution(* com.foo.service.*.*(..)) 匹配 com.foo.service 包下所有类的所有方法
execution(public User com.foo.service.UserService.*(Long)) 匹配 UserService 中返回值为 User、参数为 Long 的 public 方法
execution(* com.foo.service..*.*(..)) 匹配 com.foo.service 包及其子包下所有类的所有方法
execution(* *..*Service.*(..)) 匹配所有以 Service 结尾的类的所有方法
(2)其他常用标识符
标识符 语法示例 含义
within within(com.foo.service.*) 匹配指定包下的所有方法(比 execution 更简洁,不精确到方法)
bean bean(*Service) 匹配所有以 Service 结尾的 Bean 的所有方法(按 Bean 名称筛选)
@annotation @annotation(com.foo.annotation.Log) 匹配标注了 @Log 注解的所有方法(按注解筛选)
args args(Long, ..) 匹配第一个参数为 Long 类型、后续任意参数的方法(按参数类型筛选)

组合使用:通过 &&/||/! 组合多个表达式,如:

1
2
3
// 匹配 UserService 中标注了 @Log 注解的方法
@Pointcut("bean(userService) && @annotation(com.foo.annotation.Log)")
public void userServiceLogPointcut() {}

注意事项:无法被 AOP 代理的情况

Spring AOP 的动态代理有明确的限制,以下方法无法被增强,需避免踩坑:

代理方式 无法代理的方法类型 原因分析
JDK 动态代理 1. 非 public 修饰的方法(如 private、protected) 2. static 修饰的方法 JDK 代理基于接口,仅代理接口的 public 方法;static 方法属于类,不被接口继承
CGLIB 动态代理 1. private 修饰的方法 2. static 修饰的方法 3. final 修饰的方法 / 类 CGLIB 基于继承,private 方法无法重写;static 方法属于类;final 类无法继承,final 方法无法重写

避坑建议

  1. 业务方法尽量用 public 修饰;
  2. 避免在 static/final 方法中编写需要 AOP 增强的逻辑;
  3. 若需代理 private 方法,可通过 “包装方法” 间接实现(如 public 方法调用 private 方法,代理 public 方法)。

总结:Spring AOP 的核心价值与应用场景

Spring AOP 通过 “横切逻辑抽取 + 动态代理织入”,解决了传统开发中横切关注点的冗余与混乱问题,其核心价值体现在:

  1. 解耦:横切逻辑与业务逻辑分离,修改日志、事务时无需改动业务代码;
  2. 复用:横切逻辑一次定义,可织入到多个模块的方法中;
  3. 灵活:通过切点表达式精确控制增强范围,支持多种通知类型;
  4. 低侵入:无需修改业务类代码,仅通过配置或注解即可实现增强。

典型应用场景

  • 日志记录:方法执行前后记录请求参数、返回值、执行时间;
  • 事务管理:方法执行前开启事务,执行成功提交,异常回滚(Spring 声明式事务基于 AOP);
  • 权限控制:方法执行前校验用户权限,无权限则抛出异常;
  • 异常处理:统一捕获方法异常,记录错误日志并返回标准化响应;
  • 缓存控制:方法执行前查询缓存,存在则返回缓存值,不存在则执行方法并缓存结果

欢迎关注我的其它发布渠道

表情 | 预览
快来做第一个评论的人吧~
Powered By Valine
v1.3.10