Spring AOP的应用
# 一、Spring AOP 概述
# 1、什么是 Spring AOP
Spring AOP(Aspect-Oriented Programming,面向切面编程)是 Spring 框架中的核心模块之一。简单来说,它允许我们在不改动原有业务代码的前提下,为程序添加额外的功能。
想象一下这样的场景:你写了一个用户注册的方法,现在需要在每次用户注册时记录日志、检查权限、统计性能。传统做法是在每个相关方法里加上这些代码,但这样会让业务逻辑变得混乱。AOP 就是来解决这个问题的——它把这些"辅助功能"从主业务中分离出来,单独管理,然后在需要的时候自动"织入"到业务流程中。
# 2、Spring AOP 的核心优势
Spring AOP 之所以受到开发者青睐,主要有这几个原因:
非侵入性设计 你不需要修改现有的业务代码,只需要通过配置或注解就能为方法添加新功能。这就像给房子装修时不用拆墙,直接贴壁纸一样方便。
配置灵活简单 可以精确控制在哪些方法、什么时机执行增强逻辑。想给所有 Service 层的方法加日志?一行配置搞定。只想给特定的方法加事务?也没问题。
与 Spring 生态无缝集成 作为 Spring 框架的一部分,AOP 与 Spring MVC、Spring Boot 等其他组件配合得天衣无缝,学习成本低。
多种代理方式支持 根据不同的场景,Spring AOP 会自动选择最合适的代理方式:有接口的用 JDK 动态代理,没接口的用 CGLIB 代理。
# 3、Spring AOP 的典型应用场景
在实际开发中,Spring AOP 最常见的应用场景包括:
日志记录
自动记录方法的调用信息、参数、返回值和执行时间,这对排查问题和系统监控特别有用。
性能监控
自动统计方法执行时间,帮你快速定位性能瓶颈,而不需要在每个方法里手动添加计时代码。
事务管理
这是 Spring AOP 最经典的应用,通过 @Transactional
注解就能给方法加上事务控制,大大简化了事务管理的复杂度。
权限控制
在方法执行前检查用户权限,统一处理安全相关的逻辑。
异常处理
统一捕获和处理方法抛出的异常,避免在每个方法里写重复的异常处理代码。
# 4、Spring AOP 核心概念解析
要理解 Spring AOP,需要掌握几个关键概念:
切面(Aspect)
可以理解为一个功能模块,比如"日志记录切面"、"事务管理切面"。一个切面通常包含多个通知方法和切入点定义。
通知(Advice)
定义了"什么时候做什么事"。Spring AOP 提供了五种通知类型:
@Before
:方法执行前@After
:方法执行后(无论成功失败)@AfterReturning
:方法成功返回后@AfterThrowing
:方法抛异常时@Around
:包围整个方法执行
切入点(Pointcut)
定义了"在哪里执行",通过表达式或注解指定哪些方法需要被拦截。
连接点(JoinPoint)
程序执行过程中能够插入切面的点,在 Spring AOP 中主要指方法的执行。
织入(Weaving)
将切面逻辑融入到目标对象的过程,Spring AOP 在运行时通过代理对象实现织入。
# 二、Spring AOP 实现原理深度解析
# 1、代理模式基础:静态代理 vs 动态代理
要搞懂 Spring AOP 的工作原理,我们得先理解代理模式这个基础概念。
静态代理的工作方式
静态代理就像找了个助理帮你处理事务。这个助理(代理类)和你(被代理类)的关系在编译时就确定了,助理知道你有哪些方法,会在调用你的方法前后做一些额外工作。
静态代理的问题很明显:如果你的方法变了,助理也得跟着调整。更要命的是,每个需要代理的类都得手写一个对应的代理类,工作量巨大。
动态代理的优势
动态代理则聪明得多,它在程序运行时才"临时"生成代理类。就像有个万能助理,能够根据当前情况自动适配,处理各种不同的委托任务。
Spring AOP 正是基于动态代理实现的,主要使用两种技术:JDK 动态代理和 CGLIB 动态代理。
# 2、JDK 动态代理:基于接口的代理方案
JDK 动态代理是 Java 原生提供的代理技术,它有个重要特点:只能为实现了接口的类创建代理。
工作原理
JDK 动态代理的核心是两个组件:Proxy
类和 InvocationHandler
接口。简单来说,Proxy
负责生成代理对象,InvocationHandler
负责定义代理逻辑。
当你调用代理对象的方法时,实际上调用的是 InvocationHandler.invoke()
方法,在这个方法里你可以:
- 在目标方法执行前做些事情(比如记录日志)
- 调用真正的目标方法
- 在目标方法执行后做些事情(比如清理资源)
实战示例:用户服务的日志代理
让我们通过一个实际例子来理解 JDK 动态代理的工作流程。假设我们有一个用户服务,需要在每次操作时记录日志。
首先定义服务接口:
public interface UserService {
void saveUser(String username);
User findUser(Long id);
}
然后实现具体的服务逻辑:
public class UserServiceImpl implements UserService {
@Override
public void saveUser(String username) {
System.out.println("保存用户:" + username);
}
@Override
public User findUser(Long id) {
System.out.println("查找用户ID:" + id);
return new User(id, "用户" + id);
}
}
创建代理处理器,在这里添加日志逻辑:
public class LoggingInvocationHandler implements InvocationHandler {
private final Object target;
public LoggingInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 方法执行前记录日志
System.out.println("开始执行方法:" + method.getName());
System.out.println("方法参数:" + Arrays.toString(args));
long startTime = System.currentTimeMillis();
Object result = method.invoke(target, args);
long endTime = System.currentTimeMillis();
// 方法执行后记录日志
System.out.println("方法执行完成,耗时:" + (endTime - startTime) + "ms");
return result;
}
}
最后创建代理对象并使用:
public class ProxyDemo {
public static void main(String[] args) {
// 创建真实对象
UserService realService = new UserServiceImpl();
// 创建代理对象
UserService proxyService = (UserService) Proxy.newProxyInstance(
realService.getClass().getClassLoader(),
realService.getClass().getInterfaces(),
new LoggingInvocationHandler(realService)
);
// 使用代理对象
proxyService.saveUser("张三");
proxyService.findUser(1L);
}
}
运行结果:
开始执行方法:saveUser
方法参数:[张三]
保存用户:张三
方法执行完成,耗时:2ms
开始执行方法:findUser
方法参数:[1]
查找用户ID:1
方法执行完成,耗时:1ms
JDK 动态代理的限制
需要注意的是,JDK 动态代理有个重要限制:目标类必须实现至少一个接口。如果你的类没有实现任何接口,JDK 动态代理就无能为力了,这时候就需要用到 CGLIB 动态代理。
# 3、CGLIB 动态代理:基于继承的代理方案
当目标类没有实现接口时,Spring AOP 会自动切换到 CGLIB 动态代理。CGLIB(Code Generation Library)是一个强大的字节码生成库,它通过继承目标类来创建代理对象。
工作原理
CGLIB 动态代理的思路很巧妙:既然不能基于接口做代理,那就通过继承来实现。它会在运行时生成目标类的子类,然后重写其中的方法,在重写的方法中加入代理逻辑。
核心组件是 Enhancer
类和 MethodInterceptor
接口:
Enhancer
:负责创建代理类,相当于 JDK 动态代理中的Proxy
类MethodInterceptor
:定义拦截逻辑,相当于 JDK 动态代理中的InvocationHandler
实战示例:订单服务的性能监控
让我们通过一个例子来看看 CGLIB 是如何为没有接口的类创建代理的。假设我们有一个订单服务,需要监控每个方法的执行性能。
首先引入 CGLIB 依赖(Spring Boot 项目中通常已经包含):
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
定义一个没有接口的订单服务:
public class OrderService {
public void createOrder(String orderNo, BigDecimal amount) {
System.out.println("创建订单:" + orderNo + ",金额:" + amount);
// 模拟业务处理耗时
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
public Order findOrder(String orderNo) {
System.out.println("查询订单:" + orderNo);
return new Order(orderNo, new BigDecimal("299.99"));
}
}
创建性能监控的拦截器:
public class PerformanceInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
long startTime = System.currentTimeMillis();
System.out.println("开始执行方法:" + method.getName());
// 调用原始方法
Object result = proxy.invokeSuper(obj, args);
long endTime = System.currentTimeMillis();
System.out.println("方法 " + method.getName() + " 执行完成,耗时:" + (endTime - startTime) + "ms");
return result;
}
}
使用 CGLIB 创建代理对象:
public class CglibDemo {
public static void main(String[] args) {
// 创建增强器
Enhancer enhancer = new Enhancer();
// 设置父类(要被代理的类)
enhancer.setSuperclass(OrderService.class);
// 设置回调函数(拦截器)
enhancer.setCallback(new PerformanceInterceptor());
// 创建代理对象
OrderService proxyService = (OrderService) enhancer.create();
// 使用代理对象
proxyService.createOrder("ORD001", new BigDecimal("299.99"));
proxyService.findOrder("ORD001");
}
}
运行结果:
开始执行方法:createOrder
创建订单:ORD001,金额:299.99
方法 createOrder 执行完成,耗时:102ms
开始执行方法:findOrder
查询订单:ORD001
方法 findOrder 执行完成,耗时:1ms
CGLIB 代理的特点和限制
优势:
- 可以代理没有接口的普通类
- 性能通常比 JDK 动态代理稍好
- 功能更加灵活强大
限制:
- 无法代理
final
类和final
方法(因为无法继承和重写) - 无法代理
private
方法(子类无法访问) - 构造函数会被调用两次(父类构造函数 + 子类构造函数)
# 4、Spring AOP 的智能代理选择机制
Spring AOP 的聪明之处在于它会根据目标对象的特点自动选择最合适的代理方式,开发者无需手动配置。
代理选择策略
Spring AOP 的代理选择遵循以下规则:
有接口?用 JDK 动态代理
如果目标类实现了一个或多个接口,Spring 会优先选择 JDK 动态代理。这样生成的代理对象更轻量,性能也不错。没接口?用 CGLIB 代理
如果目标类没有实现任何接口,Spring 会自动切换到 CGLIB 代理,通过继承的方式创建代理对象。强制使用 CGLIB?也可以
你也可以通过配置强制使用 CGLIB 代理,即使目标类有接口。
拦截器链的执行流程
Spring AOP 的核心是拦截器链(Interceptor Chain)机制。当你调用代理对象的方法时,实际的执行流程是这样的:
- 代理对象接收方法调用
- 构建拦截器链:Spring 根据切点匹配规则,找出所有需要应用到当前方法的通知
- 按顺序执行拦截器:按照通知的优先级顺序执行
- 调用目标方法:在合适的时机调用真正的业务方法
- 返回结果:将最终结果返回给调用者
这个过程就像过安检一样:你的方法调用要经过一道道"检查点"(各种通知),每个检查点都可能对你的调用进行处理,最后才到达真正的目标方法。
# 4.1、Spring AOP 与 AspectJ 的关系
很多人会好奇:Spring AOP 和 AspectJ 到底是什么关系?简单来说,Spring AOP 可以理解为 AspectJ 的"轻量级版本"。
AspectJ:AOP 的鼻祖
AspectJ 是 AOP 领域的老大哥,功能非常强大和全面。它提供了完整的 AOP 语言特性,可以在编译时、类加载时、运行时等多个阶段进行织入。但是功能强大也意味着复杂度高,学习成本比较大。
Spring AOP:实用主义的选择
Spring AOP 借鉴了 AspectJ 的核心思想和语法,但做了大量简化:
- 借用了 AspectJ 的切点表达式语法:比如
execution(* com.example.service.*.*(..))
- 使用了 AspectJ 的注解:
@Aspect
、@Before
、@After
等 - 但只在运行时织入:相比 AspectJ 的多种织入方式,Spring AOP 只支持运行时织入
- 功能有所限制:只能拦截方法调用,不能拦截字段访问、构造函数等
两者的选择建议
选择 Spring AOP 的场景:
- 项目使用 Spring 框架
- 主要需求是方法级别的拦截(日志、事务、权限等)
- 希望配置简单,学习成本低
- 需要与 Spring 生态无缝集成
选择 AspectJ 的场景:
- 需要更精细的控制(字段访问、构造函数拦截等)
- 对性能要求极高(编译时织入性能更好)
- 不依赖 Spring 框架
- 需要 AspectJ 的高级特性
对于大多数 Spring 项目来说,Spring AOP 已经完全够用了,它在简单性和功能性之间找到了很好的平衡点。
# 三、Spring AOP 实战指南
# 1、启用 Spring AOP 功能
在开始使用 Spring AOP 之前,我们需要先启用 AOP 功能。这个过程很简单,只需要一个注解就能搞定。
基础配置
在 Spring 配置类上添加 @EnableAspectJAutoProxy
注解:
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
// 其他配置...
}
如果你使用的是 Spring Boot,这个注解通常已经自动配置了,不需要手动添加。
高级配置选项
@EnableAspectJAutoProxy
提供了两个重要属性:
proxyTargetClass:是否强制使用 CGLIB 代理,默认为
false
。设置为true
时,即使目标类有接口也会使用 CGLIB 代理。exposeProxy:是否暴露当前代理对象,默认为
false
。设置为true
时,可以通过AopContext.currentProxy()
获取当前代理对象,这在处理同类方法调用时很有用。
@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)
@Configuration
public class AopConfig {
// 配置内容
}
# 2、掌握切点表达式:精确定位目标方法
切点表达式是 Spring AOP 的核心,它决定了哪些方法会被拦截。掌握切点表达式,就能精确控制 AOP 的作用范围。
# 2.1、切点表达式语法详解
Spring AOP 支持 9 种 AspectJ 切点表达式函数,每种都有其特定的使用场景:
1. execution() - 最常用的方法匹配
// 匹配 service 包下所有类的所有方法
execution(* com.example.service.*.*(..))
// 匹配所有 public 方法
execution(public * *(..))
// 匹配返回 String 类型的方法
execution(String com.example.service.*.*(..))
// 匹配 UserService 类的 save 开头的方法
execution(* com.example.service.UserService.save*(..))
2. within() - 按类或包匹配
// 匹配 service 包下所有类的所有方法
within(com.example.service.*)
// 匹配 service 包及其子包下的所有方法
within(com.example.service..*)
// 匹配指定类的所有方法
within(com.example.service.UserService)
3. @annotation() - 按注解匹配(最实用)
// 匹配标记了 @Transactional 的方法
@annotation(org.springframework.transaction.annotation.Transactional)
// 匹配标记了 @Cacheable 的方法
@annotation(org.springframework.cache.annotation.Cacheable)
// 匹配自定义注解
@annotation(com.example.annotation.LogExecutionTime)
4. @within() - 按类注解匹配
// 匹配标记了 @Service 的类的所有方法
@within(org.springframework.stereotype.Service)
// 匹配标记了 @RestController 的类的所有方法
@within(org.springframework.web.bind.annotation.RestController)
5. args() - 按参数类型匹配
// 匹配只有一个 String 参数的方法
args(String)
// 匹配第一个参数为 Long 类型的方法
args(Long,..)
// 匹配最后一个参数为 String 类型的方法
args(..,String)
其他几种表达式(this()
、target()
、@target()
、@args()
)在实际项目中使用较少,主要用于特殊场景。
# 2.2、通配符和逻辑运算符
通配符的使用
*
:匹配任意字符,常用于方法名、类名、返回值类型..
:匹配任意数量的参数或包路径+
:匹配类及其子类(较少使用)
// * 的使用示例
execution(* save*(..)) // 匹配所有以save开头的方法
execution(* com.example.*.*(..)) // 匹配com.example下一级包的所有方法
// .. 的使用示例
execution(* com.example..*.*(..)) // 匹配com.example及其子包的所有方法
args(..) // 匹配任意参数的方法
args(String,..) // 第一个参数是String,后面任意参数
// + 的使用示例
within(com.example.BaseService+) // 匹配BaseService及其子类
逻辑运算符组合
// AND 组合(&& 或 and)
@Pointcut("execution(* com.example.service.*.*(..)) && @annotation(org.springframework.transaction.annotation.Transactional)")
// OR 组合(|| 或 or)
@Pointcut("within(com.example.service.*) || within(com.example.controller.*)")
// NOT 组合(! 或 not)
@Pointcut("execution(* com.example.service.*.*(..)) && !execution(* com.example.service.*.get*(..))")
# 2.3、创建切面类
切面类是 AOP 功能的载体,它包含了切点定义和通知方法。创建一个切面类非常简单:
@Aspect // 标识这是一个切面类
@Component // 注册为Spring Bean
public class LoggingAspect {
// 定义切点:匹配service包下所有方法
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceLayer() {}
// 定义切点:匹配标记了@Transactional的方法
@Pointcut("@annotation(org.springframework.transaction.annotation.Transactional)")
public void transactionalMethods() {}
// 前置通知:在目标方法执行前记录日志
@Before("serviceLayer()")
public void logBefore(JoinPoint joinPoint) {
System.out.println("开始执行方法:" + joinPoint.getSignature().getName());
}
// 后置通知:在目标方法成功返回后记录日志
@AfterReturning(pointcut = "serviceLayer()", returning = "result")
public void logAfterReturning(JoinPoint joinPoint, Object result) {
System.out.println("方法执行成功:" + joinPoint.getSignature().getName() +
",返回值:" + result);
}
}
切面类的关键要素:
@Aspect
:标识这是一个切面类@Component
:确保能被 Spring 容器管理@Pointcut
:定义可重用的切点- 各种通知注解:定义具体的增强逻辑
# 3、五种通知类型详解
Spring AOP 提供了五种通知类型,每种都有其特定的使用场景。掌握这些通知类型,就能灵活应对各种业务需求。
# 3.1、前置通知(@Before)
前置通知在目标方法执行前触发,是最常用的通知类型之一。它特别适合做权限检查、参数验证、日志记录等预处理工作。
实战示例:用户权限检查
@Aspect
@Component
public class SecurityAspect {
@Before("execution(* com.example.service.UserService.deleteUser(..))")
public void checkPermission(JoinPoint joinPoint) {
// 获取当前用户
String currentUser = SecurityContextHolder.getContext().getAuthentication().getName();
// 获取方法参数
Object[] args = joinPoint.getArgs();
Long userId = (Long) args[0];
// 权限检查逻辑
if (!hasDeletePermission(currentUser, userId)) {
throw new SecurityException("当前用户无权删除该用户");
}
System.out.println("权限检查通过,允许删除用户:" + userId);
}
private boolean hasDeletePermission(String currentUser, Long targetUserId) {
// 实际的权限检查逻辑
return "admin".equals(currentUser) || currentUser.equals("user_" + targetUserId);
}
}
前置通知的特点:
- 无法阻止目标方法执行(除非抛出异常)
- 无法修改目标方法的参数
- 适合做安全检查、日志记录、参数验证
# 3.2、后置通知(@After)
后置通知无论目标方法是否正常执行都会触发,类似于 try-catch-finally
中的 finally
块。它特别适合做资源清理、日志记录等收尾工作。
实战示例:资源清理和操作记录
@Aspect
@Component
public class ResourceCleanupAspect {
@After("execution(* com.example.service.FileService.*(..))")
public void cleanupAndLog(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
// 记录操作日志
System.out.println("文件操作完成:" + methodName + " 于 " + new Date());
// 清理临时资源
cleanupTempFiles();
// 释放连接池等资源
releaseConnections();
}
private void cleanupTempFiles() {
// 清理临时文件的逻辑
System.out.println("清理临时文件...");
}
private void releaseConnections() {
// 释放连接资源的逻辑
System.out.println("释放连接资源...");
}
}
后置通知的特点:
- 无论方法正常执行还是抛出异常都会执行
- 无法获取方法的返回值
- 主要用于资源清理、统计信息收集
# 3.3、返回通知(@AfterReturning)
返回通知只在目标方法正常执行并成功返回时才会触发。它的最大优势是可以获取到方法的返回值,非常适合做返回值处理、缓存、审计日志等工作。
实战示例:缓存处理和结果审计
@Aspect
@Component
public class CacheAndAuditAspect {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private AuditLogService auditLogService;
@AfterReturning(pointcut = "execution(* com.example.service.UserService.findUser(..))",
returning = "user")
public void cacheUserAndAudit(JoinPoint joinPoint, User user) {
if (user != null) {
// 缓存用户信息
String cacheKey = "user:" + user.getId();
redisTemplate.opsForValue().set(cacheKey, user, Duration.ofMinutes(30));
// 记录审计日志
String currentUser = getCurrentUser();
auditLogService.log(currentUser + " 查询了用户信息:" + user.getUsername());
System.out.println("用户信息已缓存,审计日志已记录");
}
}
@AfterReturning(pointcut = "execution(* com.example.service.OrderService.createOrder(..))",
returning = "orderId")
public void processOrderCreation(JoinPoint joinPoint, String orderId) {
// 发送订单创建通知
sendOrderNotification(orderId);
// 更新统计信息
updateOrderStatistics();
System.out.println("订单创建后续处理完成:" + orderId);
}
private String getCurrentUser() {
return SecurityContextHolder.getContext().getAuthentication().getName();
}
private void sendOrderNotification(String orderId) {
// 发送通知的逻辑
System.out.println("发送订单创建通知:" + orderId);
}
private void updateOrderStatistics() {
// 更新统计信息的逻辑
System.out.println("更新订单统计信息");
}
}
返回通知的特点:
- 只有方法正常返回时才执行(抛异常时不执行)
- 可以获取方法的返回值进行处理
- 适合做缓存、后续业务处理、结果审计
# 3.4、异常通知(@AfterThrowing)
异常通知在目标方法抛出异常时触发,是异常处理和监控的重要手段。它可以用来记录异常日志、发送告警通知、进行异常统计等。
实战示例:异常监控和告警
@Aspect
@Component
public class ExceptionMonitorAspect {
@Autowired
private NotificationService notificationService;
@Autowired
private MetricsService metricsService;
private static final Logger logger = LoggerFactory.getLogger(ExceptionMonitorAspect.class);
@AfterThrowing(pointcut = "execution(* com.example.service.PaymentService.*(..))",
throwing = "exception")
public void handlePaymentException(JoinPoint joinPoint, Exception exception) {
// 记录详细的异常日志
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
logger.error("支付服务异常 - 方法: {}, 参数: {}, 异常: {}",
methodName, Arrays.toString(args), exception.getMessage(), exception);
// 统计异常次数
metricsService.incrementCounter("payment.service.error",
"method", methodName,
"exception", exception.getClass().getSimpleName());
// 发送告警通知(针对严重异常)
if (isCriticalException(exception)) {
notificationService.sendAlert("支付服务严重异常",
"方法: " + methodName + ", 异常: " + exception.getMessage());
}
}
@AfterThrowing(pointcut = "execution(* com.example.service.UserService.*(..))",
throwing = "exception")
public void handleUserServiceException(JoinPoint joinPoint, Exception exception) {
String methodName = joinPoint.getSignature().getName();
// 根据异常类型进行不同处理
if (exception instanceof ValidationException) {
logger.warn("用户服务参数验证失败 - 方法: {}, 异常: {}", methodName, exception.getMessage());
} else if (exception instanceof DataAccessException) {
logger.error("用户服务数据访问异常 - 方法: {}, 异常: {}", methodName, exception.getMessage(), exception);
// 数据库异常需要特别关注
notificationService.sendAlert("数据库访问异常", "用户服务出现数据访问问题");
} else {
logger.error("用户服务未知异常 - 方法: {}, 异常: {}", methodName, exception.getMessage(), exception);
}
}
private boolean isCriticalException(Exception exception) {
return exception instanceof PaymentTimeoutException
|| exception instanceof PaymentSecurityException
|| exception instanceof DataAccessException;
}
}
异常通知的特点:
- 只在方法抛出异常时才执行
- 可以获取具体的异常对象进行处理
- 不会阻止异常的传播(异常仍会继续向上抛出)
- 适合做异常监控、日志记录、告警通知
# 3.5、环绕通知(@Around)
环绕通知是功能最强大的通知类型,它完全包围目标方法的执行。你可以在方法执行前后做任何事情,甚至可以决定是否执行目标方法、修改参数、处理返回值等。
实战示例:性能监控和缓存管理
@Aspect
@Component
public class PerformanceAndCacheAspect {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
private static final Logger logger = LoggerFactory.getLogger(PerformanceAndCacheAspect.class);
@Around("@annotation(com.example.annotation.Cacheable)")
public Object cacheableMethod(ProceedingJoinPoint joinPoint) throws Throwable {
// 生成缓存key
String cacheKey = generateCacheKey(joinPoint);
// 先尝试从缓存获取
Object cachedResult = redisTemplate.opsForValue().get(cacheKey);
if (cachedResult != null) {
logger.info("从缓存获取数据: {}", cacheKey);
return cachedResult;
}
// 缓存未命中,执行目标方法
long startTime = System.currentTimeMillis();
Object result = joinPoint.proceed();
long executionTime = System.currentTimeMillis() - startTime;
// 将结果存入缓存
if (result != null) {
redisTemplate.opsForValue().set(cacheKey, result, Duration.ofMinutes(10));
logger.info("数据已缓存: {}", cacheKey);
}
logger.info("方法 {} 执行时间: {}ms", joinPoint.getSignature().getName(), executionTime);
return result;
}
@Around("execution(* com.example.service.TransactionService.*(..))")
public Object transactionWithRetry(ProceedingJoinPoint joinPoint) throws Throwable {
String methodName = joinPoint.getSignature().getName();
int maxRetries = 3;
int retryCount = 0;
while (retryCount < maxRetries) {
try {
long startTime = System.currentTimeMillis();
Object result = joinPoint.proceed();
long executionTime = System.currentTimeMillis() - startTime;
// 记录成功的执行
logger.info("事务方法 {} 执行成功,耗时: {}ms,重试次数: {}",
methodName, executionTime, retryCount);
return result;
} catch (TransientException e) {
retryCount++;
if (retryCount >= maxRetries) {
logger.error("事务方法 {} 重试 {} 次后仍然失败", methodName, maxRetries);
throw e;
}
// 等待一段时间后重试
long waitTime = retryCount * 1000L; // 递增等待时间
logger.warn("事务方法 {} 第 {} 次执行失败,{}ms 后重试", methodName, retryCount, waitTime);
Thread.sleep(waitTime);
} catch (Exception e) {
// 非瞬时异常,直接抛出
logger.error("事务方法 {} 发生非瞬时异常,不进行重试", methodName, e);
throw e;
}
}
return null; // 这行实际不会执行到
}
private String generateCacheKey(ProceedingJoinPoint joinPoint) {
StringBuilder keyBuilder = new StringBuilder();
keyBuilder.append(joinPoint.getSignature().getDeclaringTypeName())
.append(":")
.append(joinPoint.getSignature().getName());
Object[] args = joinPoint.getArgs();
if (args != null && args.length > 0) {
keyBuilder.append(":");
for (Object arg : args) {
keyBuilder.append(arg != null ? arg.toString() : "null").append(",");
}
}
return keyBuilder.toString();
}
}
环绕通知的特点:
- 功能最强大,可以完全控制方法的执行
- 可以修改参数、返回值,甚至阻止方法执行
- 必须调用
ProceedingJoinPoint.proceed()
来执行目标方法 - 适合做性能监控、缓存、事务管理、重试机制
# 3.6、引介通知(@DeclareParents)
引介通知是一个比较高级的特性,它可以为现有的类动态添加新的接口实现,而无需修改原有代码。这在需要为第三方类库添加新功能时特别有用。
实战示例:为现有类添加审计功能
// 定义审计接口
public interface Auditable {
void setCreatedBy(String user);
void setCreatedTime(LocalDateTime time);
String getCreatedBy();
LocalDateTime getCreatedTime();
}
// 审计接口的默认实现
public class AuditableImpl implements Auditable {
private String createdBy;
private LocalDateTime createdTime;
@Override
public void setCreatedBy(String user) {
this.createdBy = user;
}
@Override
public void setCreatedTime(LocalDateTime time) {
this.createdTime = time;
}
@Override
public String getCreatedBy() {
return createdBy;
}
@Override
public LocalDateTime getCreatedTime() {
return createdTime;
}
}
// 现有的业务类(假设这是第三方库的类,我们无法修改)
public class Product {
private String name;
private BigDecimal price;
public Product(String name, BigDecimal price) {
this.name = name;
this.price = price;
}
// getter/setter方法...
}
// 使用引介通知为Product类添加审计功能
@Aspect
@Component
public class AuditAspect {
// 为com.example.model包下的所有类添加Auditable接口
@DeclareParents(value = "com.example.model.*", defaultImpl = AuditableImpl.class)
private Auditable auditable;
// 在对象创建时自动设置审计信息
@AfterReturning(pointcut = "execution(com.example.model.*.new(..))", returning = "result")
public void setAuditInfo(Object result) {
if (result instanceof Auditable) {
Auditable auditableObj = (Auditable) result;
auditableObj.setCreatedBy(getCurrentUser());
auditableObj.setCreatedTime(LocalDateTime.now());
}
}
private String getCurrentUser() {
// 获取当前用户的逻辑
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
return auth != null ? auth.getName() : "system";
}
}
// 使用示例
@Service
public class ProductService {
public void createProduct(String name, BigDecimal price) {
Product product = new Product(name, price);
// 由于引介通知,product现在也实现了Auditable接口
if (product instanceof Auditable) {
Auditable auditableProduct = (Auditable) product;
System.out.println("产品创建者:" + auditableProduct.getCreatedBy());
System.out.println("创建时间:" + auditableProduct.getCreatedTime());
}
// 保存产品...
}
}
引介通知的特点:
- 可以为现有类动态添加新接口,无需修改原有代码
- 适合为第三方类库添加额外功能
- 使用场景相对较少,但在特定情况下非常有用
- 需要谨慎使用,避免破坏原有类的设计
# 4、通知执行顺序和优先级控制
在实际项目中,往往会有多个切面同时作用于一个方法,理解和控制这些切面的执行顺序非常重要。
执行顺序规则
- 同一切面内的通知:按照方法在类中的定义顺序执行
- 不同切面间的通知:可以通过
@Order
注解或实现Ordered
接口来控制 - 未指定顺序的切面:执行顺序是不确定的,不要依赖默认顺序
实战示例:多切面优先级控制
// 安全检查切面 - 最高优先级
@Aspect
@Component
@Order(1)
public class SecurityAspect {
@Before("execution(* com.example.service.*.*(..))")
public void checkSecurity(JoinPoint joinPoint) {
System.out.println("1. 执行安全检查");
// 安全检查逻辑...
}
@AfterReturning("execution(* com.example.service.*.*(..))")
public void securityAfter() {
System.out.println("6. 安全检查完成");
}
}
// 日志记录切面 - 中等优先级
@Aspect
@Component
@Order(2)
public class LoggingAspect {
@Before("execution(* com.example.service.*.*(..))")
public void logBefore(JoinPoint joinPoint) {
System.out.println("2. 记录方法调用日志");
// 日志记录逻辑...
}
@AfterReturning("execution(* com.example.service.*.*(..))")
public void logAfter() {
System.out.println("5. 记录方法返回日志");
}
}
// 性能监控切面 - 较低优先级
@Aspect
@Component
@Order(3)
public class PerformanceAspect {
@Around("execution(* com.example.service.*.*(..))")
public Object measurePerformance(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("3. 开始性能监控");
long startTime = System.currentTimeMillis();
Object result = joinPoint.proceed(); // 4. 执行目标方法
long endTime = System.currentTimeMillis();
System.out.println("4. 性能监控结束,耗时:" + (endTime - startTime) + "ms");
return result;
}
}
执行顺序示例 当调用一个被多个切面拦截的方法时,执行顺序如下:
1. 执行安全检查 (@Order(1) @Before)
2. 记录方法调用日志 (@Order(2) @Before)
3. 开始性能监控 (@Order(3) @Around - 前半部分)
4. 执行目标方法 (实际业务方法)
4. 性能监控结束 (@Order(3) @Around - 后半部分)
5. 记录方法返回日志 (@Order(2) @AfterReturning)
6. 安全检查完成 (@Order(1) @AfterReturning)
优先级控制的最佳实践
- 事务管理:通常设置最高优先级(@Order(0) 或更小的值)
- 安全检查:设置较高优先级(@Order(1-10))
- 日志记录:设置中等优先级(@Order(10-50))
- 性能监控:设置较低优先级(@Order(50-100))
# 5、深入理解连接点信息
JoinPoint
是我们在通知方法中获取目标方法详细信息的窗口。掌握这些信息的获取方式,能让我们编写更智能、更灵活的切面逻辑。
JoinPoint 核心方法解析
@Aspect
@Component
public class DetailedLoggingAspect {
private static final Logger logger = LoggerFactory.getLogger(DetailedLoggingAspect.class);
@Before("execution(* com.example.service.*.*(..))")
public void logMethodDetails(JoinPoint joinPoint) {
// 获取方法签名信息
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
// 获取类名和方法名
String className = signature.getDeclaringTypeName();
String methodName = signature.getName();
// 获取方法参数
Object[] args = joinPoint.getArgs();
String[] paramNames = signature.getParameterNames();
Class<?>[] paramTypes = signature.getParameterTypes();
// 获取目标对象和代理对象
Object target = joinPoint.getTarget();
Object proxy = joinPoint.getThis();
// 构建详细的日志信息
StringBuilder logMessage = new StringBuilder();
logMessage.append("调用方法: ").append(className).append(".").append(methodName).append("(");
for (int i = 0; i < args.length; i++) {
if (i > 0) logMessage.append(", ");
logMessage.append(paramTypes[i].getSimpleName())
.append(" ")
.append(paramNames[i])
.append("=")
.append(args[i]);
}
logMessage.append(")");
logMessage.append(" | 目标对象: ").append(target.getClass().getSimpleName());
logMessage.append(" | 代理对象: ").append(proxy.getClass().getSimpleName());
logger.info(logMessage.toString());
}
}
ProceedingJoinPoint 高级用法
@Aspect
@Component
public class SmartCacheAspect {
@Around("@annotation(com.example.annotation.SmartCache)")
public Object smartCacheMethod(ProceedingJoinPoint joinPoint) throws Throwable {
// 获取方法信息
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
// 检查是否需要缓存
SmartCache cacheAnnotation = method.getAnnotation(SmartCache.class);
boolean useCache = cacheAnnotation.enabled();
if (!useCache) {
// 不使用缓存,直接执行原方法
return joinPoint.proceed();
}
// 生成缓存key
String cacheKey = generateCacheKey(joinPoint);
// 尝试从缓存获取
Object cachedResult = getCachedResult(cacheKey);
if (cachedResult != null) {
logger.info("缓存命中: {}", cacheKey);
return cachedResult;
}
// 执行原方法
Object result = joinPoint.proceed();
// 缓存结果
if (result != null) {
cacheResult(cacheKey, result, cacheAnnotation.ttl());
}
return result;
}
@Around("execution(* com.example.service.*.save*(..))")
public Object validateAndSave(ProceedingJoinPoint joinPoint) throws Throwable {
Object[] args = joinPoint.getArgs();
// 参数验证
for (Object arg : args) {
if (arg == null) {
throw new IllegalArgumentException("保存操作的参数不能为空");
}
// 如果参数有验证注解,进行验证
if (hasValidationAnnotations(arg)) {
validateObject(arg);
}
}
// 如果需要修改参数,可以这样做
Object[] modifiedArgs = preprocessArguments(args);
// 使用修改后的参数执行方法
return joinPoint.proceed(modifiedArgs);
}
private String generateCacheKey(ProceedingJoinPoint joinPoint) {
StringBuilder keyBuilder = new StringBuilder();
keyBuilder.append(joinPoint.getSignature().getDeclaringTypeName())
.append(":")
.append(joinPoint.getSignature().getName());
Object[] args = joinPoint.getArgs();
if (args.length > 0) {
keyBuilder.append(":");
for (Object arg : args) {
keyBuilder.append(arg != null ? arg.hashCode() : "null").append(",");
}
}
return keyBuilder.toString();
}
private Object[] preprocessArguments(Object[] args) {
// 参数预处理逻辑
Object[] modifiedArgs = new Object[args.length];
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof String) {
// 字符串参数去除首尾空格
modifiedArgs[i] = ((String) args[i]).trim();
} else {
modifiedArgs[i] = args[i];
}
}
return modifiedArgs;
}
}
连接点信息的实用技巧
getArgs()
:获取方法参数,可用于日志记录、参数验证getSignature()
:获取方法签名,包含方法名、参数类型、返回值类型等getTarget()
:获取真实的目标对象,适合获取对象状态getThis()
:获取代理对象,通常用于特殊的代理逻辑proceed(args)
:使用修改后的参数执行目标方法,实现参数预处理
# 6、参数绑定:让切面更智能
参数绑定是 Spring AOP 的一个强大特性,它允许我们直接在通知方法中获取目标方法的参数、注解等信息,让切面逻辑更加智能和灵活。
基于 args() 的参数绑定
@Aspect
@Component
public class ParameterBindingAspect {
// 绑定具体类型的参数
@Before("execution(* com.example.service.UserService.updateUser(..)) && args(userId, userInfo)")
public void beforeUpdateUser(Long userId, UserInfo userInfo) {
System.out.println("即将更新用户ID: " + userId);
System.out.println("用户信息: " + userInfo.getName() + ", " + userInfo.getEmail());
// 可以在这里做参数验证
if (userId == null || userId <= 0) {
throw new IllegalArgumentException("无效的用户ID");
}
if (userInfo.getEmail() == null || !userInfo.getEmail().contains("@")) {
throw new IllegalArgumentException("无效的邮箱地址");
}
}
// 绑定第一个参数
@AfterReturning(pointcut = "execution(* com.example.service.ProductService.findProduct(..)) && args(productId,..)",
returning = "product")
public void afterFindProduct(Long productId, Product product) {
if (product != null) {
System.out.println("成功找到产品ID: " + productId + ", 产品名称: " + product.getName());
} else {
System.out.println("未找到产品ID: " + productId);
}
}
}
基于注解的参数绑定
// 自定义审计注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Audit {
String operation() default "";
String description() default "";
boolean logParams() default true;
}
@Aspect
@Component
public class AuditAspect {
@Autowired
private AuditLogService auditLogService;
// 绑定注解和参数
@Around("@annotation(audit) && args(userId,..)")
public Object auditUserOperation(ProceedingJoinPoint joinPoint, Audit audit, Long userId) throws Throwable {
long startTime = System.currentTimeMillis();
String operation = audit.operation();
String description = audit.description();
// 记录操作开始
auditLogService.logStart(userId, operation, description);
try {
Object result = joinPoint.proceed();
long duration = System.currentTimeMillis() - startTime;
// 记录操作成功
auditLogService.logSuccess(userId, operation, duration, result);
return result;
} catch (Exception e) {
long duration = System.currentTimeMillis() - startTime;
// 记录操作失败
auditLogService.logFailure(userId, operation, duration, e.getMessage());
throw e;
}
}
// 绑定注解信息
@Before("@annotation(cacheable)")
public void beforeCacheableMethod(JoinPoint joinPoint, org.springframework.cache.annotation.Cacheable cacheable) {
String[] cacheNames = cacheable.value();
String key = cacheable.key();
String condition = cacheable.condition();
System.out.println("缓存配置 - 缓存名称: " + Arrays.toString(cacheNames) +
", Key: " + key + ", 条件: " + condition);
}
}
// 使用示例
@Service
public class UserService {
@Audit(operation = "DELETE_USER", description = "删除用户账户")
public void deleteUser(Long userId) {
// 删除用户的业务逻辑
System.out.println("删除用户: " + userId);
}
@Audit(operation = "UPDATE_PROFILE", description = "更新用户资料", logParams = true)
public void updateUserProfile(Long userId, UserProfile profile) {
// 更新用户资料的业务逻辑
System.out.println("更新用户 " + userId + " 的资料");
}
}
高级参数绑定技巧
@Aspect
@Component
public class AdvancedParameterBindingAspect {
// 绑定目标对象
@Before("execution(* com.example.service.*.*(..)) && target(service)")
public void beforeServiceMethod(JoinPoint joinPoint, Object service) {
String serviceName = service.getClass().getSimpleName();
String methodName = joinPoint.getSignature().getName();
System.out.println("调用服务: " + serviceName + "." + methodName);
// 可以根据服务类型做不同的处理
if (service instanceof UserService) {
System.out.println("这是用户服务的调用");
} else if (service instanceof OrderService) {
System.out.println("这是订单服务的调用");
}
}
// 绑定多个参数和注解
@Around("@annotation(transactional) && execution(* com.example.service.*.save*(..)) && args(entity,..)")
public Object aroundSaveMethod(ProceedingJoinPoint joinPoint,
org.springframework.transaction.annotation.Transactional transactional,
Object entity) throws Throwable {
// 检查事务配置
String propagation = transactional.propagation().name();
String isolation = transactional.isolation().name();
System.out.println("事务配置 - 传播级别: " + propagation + ", 隔离级别: " + isolation);
// 实体验证
if (entity == null) {
throw new IllegalArgumentException("保存的实体不能为空");
}
// 执行保存操作
return joinPoint.proceed();
}
}
参数绑定的实用场景
- 参数验证:直接在切面中验证方法参数
- 审计日志:记录具体的操作参数和注解信息
- 缓存管理:根据参数动态生成缓存key
- 权限控制:基于用户ID等参数进行权限检查
- 性能监控:针对特定参数值进行性能统计
# 四、编程式 AOP:手动创建代理对象
在大多数情况下,我们使用注解来配置 AOP 就足够了。但在某些特殊场景下,比如需要动态创建代理、第三方集成、或者框架开发时,编程式创建代理就派上用场了。
# 1、ProxyFactoryBean:XML 时代的代理工厂
ProxyFactoryBean
是 Spring 早期提供的代理创建工具,虽然现在用得不多,但理解它有助于我们更好地理解 Spring AOP 的工作原理。
基本用法示例
// 目标服务类
@Service
public class OrderService {
public void createOrder(String orderNo) {
System.out.println("创建订单:" + orderNo);
}
public void cancelOrder(String orderNo) {
System.out.println("取消订单:" + orderNo);
}
}
// 拦截器(通知)
public class LoggingInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("方法调用前:" + invocation.getMethod().getName());
Object result = invocation.proceed();
System.out.println("方法调用后:" + invocation.getMethod().getName());
return result;
}
}
// Spring Boot 配置类方式
@Configuration
public class ProxyConfig {
@Bean
public OrderService orderService() {
return new OrderService();
}
@Bean
public LoggingInterceptor loggingInterceptor() {
return new LoggingInterceptor();
}
@Bean
public ProxyFactoryBean orderServiceProxy() {
ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
proxyFactoryBean.setTarget(orderService());
proxyFactoryBean.setInterceptorNames("loggingInterceptor");
return proxyFactoryBean;
}
}
ProxyFactoryBean 的特点
- 将 AOP 与 IoC 容器紧密集成
- 支持多个拦截器的链式调用
- 配置相对复杂,现在很少使用
- 主要用于 XML 配置时代
# 2、ProxyFactory:灵活的编程式代理
ProxyFactory
是更加灵活的编程式代理创建工具,它不依赖 Spring 容器,可以在任何地方使用,特别适合动态创建代理的场景。
基本使用示例
public class DynamicProxyCreator {
public static <T> T createProxy(T target, Class<T> interfaceClass) {
ProxyFactory proxyFactory = new ProxyFactory();
// 设置目标对象
proxyFactory.setTarget(target);
// 添加性能监控通知
proxyFactory.addAdvice(new PerformanceMonitorInterceptor());
// 添加异常处理通知
proxyFactory.addAdvice(new ExceptionHandlingInterceptor());
// 如果有接口,添加接口
if (interfaceClass != null) {
proxyFactory.addInterface(interfaceClass);
}
return (T) proxyFactory.getProxy();
}
public static void main(String[] args) {
// 创建原始服务
UserService originalService = new UserServiceImpl();
// 创建代理
UserService proxyService = createProxy(originalService, UserService.class);
// 使用代理服务
proxyService.findUser(1L);
proxyService.saveUser(new User("张三", "zhangsan@example.com"));
}
}
高级动态代理示例
@Component
public class DynamicProxyManager {
// 根据配置动态创建代理
public <T> T createConfigurableProxy(T target, ProxyConfig config) {
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(target);
// 根据配置添加不同的通知
if (config.isEnableLogging()) {
proxyFactory.addAdvice(new LoggingInterceptor());
}
if (config.isEnablePerformanceMonitoring()) {
proxyFactory.addAdvice(new PerformanceInterceptor());
}
if (config.isEnableCaching()) {
proxyFactory.addAdvice(new CachingInterceptor(config.getCacheConfig()));
}
if (config.isEnableTransaction()) {
proxyFactory.addAdvice(new TransactionInterceptor());
}
// 设置代理策略
proxyFactory.setProxyTargetClass(config.isForceClass());
return (T) proxyFactory.getProxy();
}
// 为第三方库创建代理
public DataSource createDataSourceProxy(DataSource originalDataSource) {
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(originalDataSource);
// 添加连接池监控
proxyFactory.addAdvice(new ConnectionPoolMonitorInterceptor());
// 添加SQL执行时间监控
proxyFactory.addAdvice(new SqlPerformanceInterceptor());
// 添加连接泄漏检测
proxyFactory.addAdvice(new ConnectionLeakDetectionInterceptor());
return (DataSource) proxyFactory.getProxy();
}
}
// 代理配置类
public class ProxyConfig {
private boolean enableLogging = false;
private boolean enablePerformanceMonitoring = false;
private boolean enableCaching = false;
private boolean enableTransaction = false;
private boolean forceClass = false;
private CacheConfig cacheConfig;
// getter/setter 方法...
}
ProxyFactory 的优势
- 完全编程式控制,灵活性极高
- 不依赖 Spring 容器,可以独立使用
- 支持运行时动态配置代理行为
- 适合框架开发和复杂的代理需求
# 3、AspectJProxyFactory:切面导向的代理创建
AspectJProxyFactory
是专门为 AspectJ 风格的切面设计的代理工厂,它的最大特点是可以直接使用 @Aspect
注解的类来创建代理,使用起来更直观、更方便。
基本使用示例
// 切面类
@Aspect
public class OrderAspect {
@Before("execution(* com.example.service.OrderService.create*(..))")
public void beforeCreateOrder(JoinPoint joinPoint) {
System.out.println("准备创建订单...");
}
@Around("execution(* com.example.service.OrderService.*(..))")
public Object aroundOrderOperation(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
System.out.println("订单操作开始:" + joinPoint.getSignature().getName());
Object result = joinPoint.proceed();
long endTime = System.currentTimeMillis();
System.out.println("订单操作完成,耗时:" + (endTime - startTime) + "ms");
return result;
}
@AfterThrowing(pointcut = "execution(* com.example.service.OrderService.*(..))", throwing = "ex")
public void handleOrderException(JoinPoint joinPoint, Exception ex) {
System.err.println("订单操作异常:" + ex.getMessage());
}
}
// 目标服务类
public class OrderService {
public void createOrder(String orderNo, BigDecimal amount) {
System.out.println("创建订单:" + orderNo + ",金额:" + amount);
}
public void cancelOrder(String orderNo) {
System.out.println("取消订单:" + orderNo);
}
}
// 代理创建器
public class AspectJProxyCreator {
public static OrderService createOrderServiceProxy() {
// 创建目标对象
OrderService target = new OrderService();
// 使用 AspectJProxyFactory 创建代理
AspectJProxyFactory proxyFactory = new AspectJProxyFactory(target);
// 添加切面
proxyFactory.addAspect(OrderAspect.class);
// 也可以添加切面实例
// proxyFactory.addAspect(new OrderAspect());
return proxyFactory.getProxy();
}
public static void main(String[] args) {
OrderService proxyService = createOrderServiceProxy();
// 测试代理功能
proxyService.createOrder("ORD001", new BigDecimal("299.99"));
proxyService.cancelOrder("ORD001");
}
}
复杂场景应用示例
// 用户管理切面
@Aspect
public class UserManagementAspect {
@Before("execution(* com.example.service.UserService.delete*(..))")
public void beforeDeleteUser(JoinPoint joinPoint) {
// 删除前的安全检查
System.out.println("执行用户删除前的安全检查");
}
@AfterReturning("execution(* com.example.service.UserService.save*(..))")
public void afterSaveUser(JoinPoint joinPoint) {
// 保存后的后续处理
System.out.println("用户保存成功,执行后续处理");
}
}
// 审计切面
@Aspect
public class AuditAspect {
@Around("execution(* com.example.service.UserService.*(..))")
public Object auditUserOperation(ProceedingJoinPoint joinPoint) throws Throwable {
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println("审计日志:用户操作 - " + methodName + ",参数:" + Arrays.toString(args));
try {
Object result = joinPoint.proceed();
System.out.println("审计日志:操作成功");
return result;
} catch (Exception e) {
System.out.println("审计日志:操作失败 - " + e.getMessage());
throw e;
}
}
}
// 多切面代理创建
public class MultiAspectProxyCreator {
public static UserService createUserServiceWithMultipleAspects() {
UserService target = new UserServiceImpl();
AspectJProxyFactory proxyFactory = new AspectJProxyFactory(target);
// 添加多个切面
proxyFactory.addAspect(UserManagementAspect.class);
proxyFactory.addAspect(AuditAspect.class);
// 设置代理策略(可选)
proxyFactory.setProxyTargetClass(true); // 强制使用 CGLIB
return proxyFactory.getProxy();
}
}
AspectJProxyFactory 的特点
- 切面导向:直接使用
@Aspect
类,更符合现代 AOP 编程习惯 - 自动解析:自动解析切面中的所有通知和切点
- 简单易用:相比 ProxyFactory,使用更简洁
- 完整支持:支持所有 AspectJ 注解和切点表达式
三种代理工厂的对比
特性 | ProxyFactoryBean | ProxyFactory | AspectJProxyFactory |
---|---|---|---|
使用场景 | XML 配置 | 编程式创建 | AspectJ 切面 |
灵活性 | 中等 | 最高 | 高 |
易用性 | 复杂 | 中等 | 最简单 |
依赖容器 | 是 | 否 | 否 |
推荐程度 | 不推荐 | 框架开发 | 现代应用 |
# 3.1、Advisor(顾问)
在Spring AOP(面向切面编程)中,Advisor
是一个核心概念,用于将 切面(Aspect) 和 通知(Advice) 结合起来,定义在哪些连接点(Join Point)上应用通知。
换句话说,Advisor
是一个完整的切面定义,包含了:
- 通知(Advice):定义在连接点上执行的具体逻辑(如前置通知、后置通知、环绕通知等)。
- 切点(Pointcut):定义哪些方法或连接点会被通知拦截。
Advisor
定义如下:
public interface Advisor {
Advice getAdvice();
boolean isPerInstance();
}
getAdvice()
:返回与Advisor
关联的通知(Advice)。isPerInstance()
:指示是否为每个目标对象实例创建一个新的Advisor
。
Spring 提供了 Advisor
的默认实现类 DefaultPointcutAdvisor
,它是最常用的 Advisor
实现。它的构造方法如下:
public DefaultPointcutAdvisor(Pointcut pointcut, Advice advice);
pointcut
:定义哪些方法会被拦截。advice
:定义拦截后执行的具体逻辑。
Advisor 与 Aspect 的区别:
- Aspect:是 AOP 中的一个概念,表示一个横切关注点(如日志、事务等)。它通常包含多个通知和切点。
- Advisor:是 Spring AOP 中的一个具体实现,用于将通知和切点组合在一起。一个
Aspect
可以包含多个Advisor
。
# 五、Spring AOP 底层 API 深度解析
当注解式的 AOP 无法满足复杂需求时,Spring AOP 的底层 API 就派上用场了。这些 API 为我们提供了更精细的控制能力,让我们能够深入理解 Spring AOP 的工作机制,并在必要时进行定制化开发。
# 1、Pointcut API:精确控制拦截范围
理解 Pointcut API 能让我们创建更灵活、更精确的切点匹配规则,这在复杂的企业级应用中特别有用。
Pointcut 接口核心组成
public interface Pointcut {
ClassFilter getClassFilter(); // 类级别的过滤
MethodMatcher getMethodMatcher(); // 方法级别的匹配
}
实战示例:性能敏感方法监控
// 自定义切点:只监控标记为性能敏感的方法
public class PerformanceSensitivePointcut implements Pointcut {
@Override
public ClassFilter getClassFilter() {
return clazz -> {
// 只对 Service 层类进行监控
return clazz.getName().contains(".service.") &&
clazz.isAnnotationPresent(Service.class);
};
}
@Override
public MethodMatcher getMethodMatcher() {
return new MethodMatcher() {
@Override
public boolean matches(Method method, Class<?> targetClass) {
// 只匹配标记了 @PerformanceSensitive 或者以 "find" 开头的方法
return method.isAnnotationPresent(PerformanceSensitive.class) ||
method.getName().startsWith("find");
}
@Override
public boolean isRuntime() {
return false; // 静态匹配,性能更好
}
@Override
public boolean matches(Method method, Class<?> targetClass, Object... args) {
return false; // 不需要运行时匹配
}
};
}
}
// 自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PerformanceSensitive {
String value() default "";
}
// 使用示例
@Service
public class UserService {
@PerformanceSensitive("用户查询接口")
public User findUserById(Long id) {
// 复杂的查询逻辑
return userRepository.findById(id);
}
public List<User> findActiveUsers() {
// 这个方法也会被监控(以find开头)
return userRepository.findByStatus("ACTIVE");
}
public void saveUser(User user) {
// 这个方法不会被监控
userRepository.save(user);
}
}
// 性能监控切面
public class PerformanceMonitoringAspect {
public static UserService createMonitoredUserService() {
UserService target = new UserService();
ProxyFactory proxyFactory = new ProxyFactory(target);
// 使用自定义切点
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor(
new PerformanceSensitivePointcut(),
new PerformanceMonitoringInterceptor()
);
proxyFactory.addAdvisor(advisor);
return (UserService) proxyFactory.getProxy();
}
}
Pointcut API 的实用技巧
- 静态匹配 vs 运行时匹配:静态匹配性能更好,运行时匹配更灵活
- 组合切点:可以使用
ComposablePointcut
组合多个切点 - 内置切点:Spring 提供了很多内置切点实现,如
NameMatchMethodPointcut
、AnnotationMatchingPointcut
等
# 2、Advice API:定制化通知逻辑
Spring AOP 提供了多种通知接口,让我们能够在底层精确控制拦截逻辑。这比注解方式更灵活,适合构建通用的 AOP 框架组件。
核心通知接口层次
// 所有通知的根接口
public interface Advice {}
// 方法级通知的基础接口
public interface MethodBeforeAdvice extends BeforeAdvice {
void before(Method method, Object[] args, Object target) throws Throwable;
}
public interface AfterReturningAdvice extends AfterAdvice {
void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable;
}
public interface MethodInterceptor extends Interceptor {
Object invoke(MethodInvocation invocation) throws Throwable;
}
实战示例:多功能监控通知
// 综合性能监控通知
public class ComprehensiveMonitoringAdvice implements MethodInterceptor {
private final MetricsCollector metricsCollector;
private final AlertService alertService;
private final AuditLogger auditLogger;
public ComprehensiveMonitoringAdvice(MetricsCollector metricsCollector,
AlertService alertService,
AuditLogger auditLogger) {
this.metricsCollector = metricsCollector;
this.alertService = alertService;
this.auditLogger = auditLogger;
}
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
Method method = invocation.getMethod();
String methodName = method.getDeclaringClass().getSimpleName() + "." + method.getName();
Object[] args = invocation.getArguments();
// 记录方法调用开始
long startTime = System.currentTimeMillis();
String correlationId = UUID.randomUUID().toString();
try {
// 记录审计日志
auditLogger.logMethodStart(correlationId, methodName, args);
// 执行目标方法
Object result = invocation.proceed();
// 计算执行时间
long executionTime = System.currentTimeMillis() - startTime;
// 收集性能指标
metricsCollector.recordMethodExecution(methodName, executionTime, true);
// 检查是否需要告警(执行时间过长)
if (executionTime > getPerformanceThreshold(method)) {
alertService.sendPerformanceAlert(methodName, executionTime);
}
// 记录成功日志
auditLogger.logMethodSuccess(correlationId, methodName, executionTime, result);
return result;
} catch (Exception e) {
long executionTime = System.currentTimeMillis() - startTime;
// 收集错误指标
metricsCollector.recordMethodExecution(methodName, executionTime, false);
// 发送错误告警
alertService.sendErrorAlert(methodName, e);
// 记录错误日志
auditLogger.logMethodError(correlationId, methodName, executionTime, e);
throw e; // 重新抛出异常
}
}
private long getPerformanceThreshold(Method method) {
// 根据方法注解或配置获取性能阈值
PerformanceThreshold annotation = method.getAnnotation(PerformanceThreshold.class);
return annotation != null ? annotation.value() : 5000L; // 默认5秒
}
}
// 专用的缓存清理通知
public class CacheEvictionAdvice implements AfterReturningAdvice {
private final CacheManager cacheManager;
public CacheEvictionAdvice(CacheManager cacheManager) {
this.cacheManager = cacheManager;
}
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
// 根据方法名和参数自动清理相关缓存
if (method.getName().startsWith("save") || method.getName().startsWith("update") || method.getName().startsWith("delete")) {
String cacheRegion = deriveCacheRegion(method, args);
cacheManager.evictCache(cacheRegion);
System.out.println("自动清理缓存区域:" + cacheRegion);
}
}
private String deriveCacheRegion(Method method, Object[] args) {
// 根据方法和参数推导缓存区域
String className = method.getDeclaringClass().getSimpleName().replace("Service", "");
return className.toLowerCase() + "_cache";
}
}
// 智能重试通知
public class SmartRetryAdvice implements MethodInterceptor {
private final int maxRetries;
private final long retryInterval;
public SmartRetryAdvice(int maxRetries, long retryInterval) {
this.maxRetries = maxRetries;
this.retryInterval = retryInterval;
}
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
Method method = invocation.getMethod();
// 检查方法是否应该重试
if (!shouldRetry(method)) {
return invocation.proceed();
}
Exception lastException = null;
for (int attempt = 1; attempt <= maxRetries; attempt++) {
try {
return invocation.proceed();
} catch (Exception e) {
lastException = e;
// 如果是最后一次尝试,直接抛出异常
if (attempt == maxRetries) {
break;
}
// 只对特定异常进行重试
if (isRetryableException(e)) {
System.out.println("方法 " + method.getName() + " 第 " + attempt + " 次执行失败," + retryInterval + "ms 后重试");
Thread.sleep(retryInterval);
} else {
// 非可重试异常,直接抛出
throw e;
}
}
}
throw lastException;
}
private boolean shouldRetry(Method method) {
return method.isAnnotationPresent(Retryable.class) ||
method.getName().contains("Remote") ||
method.getName().contains("External");
}
private boolean isRetryableException(Exception e) {
return e instanceof ConnectTimeoutException ||
e instanceof SocketTimeoutException ||
e instanceof TransientDataAccessException;
}
}
Advice API 的使用场景
- 框架开发:构建通用的 AOP 组件库
- 复杂逻辑:需要精确控制拦截流程的场景
- 性能优化:相比注解方式,API 方式性能开销更小
- 动态配置:根据运行时条件动态调整通知行为
# 3、Advisor API
Advisor 是切点和通知的组合,它将切点和通知绑定在一起。Spring 提供了 Advisor
接口及其实现类来定义 Advisor。
# 3.1、示例:自定义 Advisor
以下是一个自定义 Advisor 的完整示例,该 Advisor 结合了自定义切点和自定义前置通知。
import org.springframework.aop.PointcutAdvisor;
import org.springframework.aop.support.DefaultPointcutAdvisor;
// 自定义 Advisor 类,提供创建 Advisor 的静态方法
public class CustomAdvisor {
public static PointcutAdvisor createAdvisor() {
return new DefaultPointcutAdvisor(new CustomPointcut(), new CustomBeforeAdvice());
}
}
// 测试类,演示 Advisor 的使用
import org.springframework.aop.framework.ProxyFactory;
public class AdvisorTest {
public static void main(String[] args) {
// 创建目标对象
MyService target = new MyServiceImpl();
// 创建代理工厂
ProxyFactory proxyFactory = new ProxyFactory();
// 设置目标对象
proxyFactory.setTarget(target);
// 添加 Advisor
proxyFactory.addAdvisor(CustomAdvisor.createAdvisor());
// 创建代理对象
MyService proxy = (MyService) proxyFactory.getProxy();
// 调用代理对象的方法
proxy.doSomething();
}
}
// 自定义切点类
class CustomPointcut implements org.springframework.aop.Pointcut {
@Override
public org.springframework.aop.ClassFilter getClassFilter() {
return clazz -> clazz.getName().startsWith("com.example.service");
}
@Override
public org.springframework.aop.MethodMatcher getMethodMatcher() {
return org.springframework.aop.MethodMatcher.TRUE;
}
}
// 自定义前置通知类
class CustomBeforeAdvice implements org.springframework.aop.MethodBeforeAdvice {
@Override
public void before(java.lang.reflect.Method method, Object[] args, Object target) throws Throwable {
System.out.println("前置通知:准备执行方法 " + method.getName());
}
}
# 4、操作被通知对象
通过 Advised
接口可以对被通知对象进行操作,例如动态添加或移除通知。
# 4.1、示例:动态添加通知
以下是一个动态添加通知的完整示例,展示了如何使用 Advised
接口为代理对象添加通知。
import org.springframework.aop.framework.Advised;
import org.springframework.aop.framework.ProxyFactory;
// 自定义前置通知类
class CustomBeforeAdvice implements org.springframework.aop.MethodBeforeAdvice {
@Override
public void before(java.lang.reflect.Method method, Object[] args, Object target) throws Throwable {
System.out.println("前置通知:准备执行方法 " + method.getName());
}
}
// 测试类,演示动态添加通知的使用
public class AdvisedTest {
public static void main(String[] args) {
// 创建目标对象
MyService target = new MyServiceImpl();
// 创建代理工厂
ProxyFactory proxyFactory = new ProxyFactory();
// 设置目标对象
proxyFactory.setTarget(target);
// 创建代理对象
MyService proxy = (MyService) proxyFactory.getProxy();
// 将代理对象转换为 Advised 接口类型
Advised advised = (Advised) proxy;
// 动态添加前置通知
advised.addAdvice(new CustomBeforeAdvice());
// 调用代理对象的方法
proxy.doSomething();
}
}
# 5、使用自动代理机制
Spring 提供了自动代理机制,该机制可以自动为符合条件的 Bean 创建代理,简化了代理对象的创建过程。
# 5.1、示例:配置自动代理
以下是一个使用 XML 配置自动代理的完整示例。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 配置自动代理创建器 -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" />
<!-- 配置自定义切点 -->
<bean id="customPointcut" class="com.example.CustomPointcut" />
<!-- 配置自定义前置通知 -->
<bean id="customBeforeAdvice" class="com.example.CustomBeforeAdvice" />
<!-- 配置自定义 Advisor -->
<bean id="customAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<constructor-arg ref="customPointcut" />
<constructor-arg ref="customBeforeAdvice" />
</bean>
<!-- 配置目标服务 -->
<bean id="myService" class="com.example.MyServiceImpl" />
</beans>
// 自定义切点类
package com.example;
import org.springframework.aop.ClassFilter;
import org.springframework.aop.MethodMatcher;
import org.springframework.aop.Pointcut;
public class CustomPointcut implements Pointcut {
@Override
public ClassFilter getClassFilter() {
return clazz -> clazz.getName().startsWith("com.example.service");
}
@Override
public MethodMatcher getMethodMatcher() {
return MethodMatcher.TRUE;
}
}
// 自定义前置通知类
package com.example;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class CustomBeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("前置通知:准备执行方法 " + method.getName());
}
}
// 测试类,演示自动代理的使用
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AutoProxyTest {
public static void main(String[] args) {
// 加载 Spring 配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 获取代理对象
MyService myService = context.getBean("myService", MyService.class);
// 调用代理对象的方法
myService.doSomething();
}
}
# 6、使用 TargetSource 实现
TargetSource
用于动态获取目标对象,适用于需要动态切换目标对象的场景。
# 6.1、示例:自定义 TargetSource
以下是一个自定义 TargetSource
的完整示例,展示了如何动态获取目标对象。
import org.springframework.aop.TargetSource;
// 自定义 TargetSource 类
public class CustomTargetSource implements TargetSource {
// 获取目标对象的类类型
@Override
public Class<?> getTargetClass() {
return MyService.class;
}
// 判断目标对象是否为静态对象
@Override
public boolean isStatic() {
return false;
}
// 获取目标对象
@Override
public Object getTarget() throws Exception {
return new MyServiceImpl();
}
// 释放目标对象
@Override
public void releaseTarget(Object target) throws Exception {
// 释放目标对象的逻辑
}
}
// 测试类,演示 TargetSource 的使用
import org.springframework.aop.framework.ProxyFactory;
public class TargetSourceTest {
public static void main(String[] args) {
// 创建自定义 TargetSource 对象
CustomTargetSource targetSource = new CustomTargetSource();
// 创建代理工厂
ProxyFactory proxyFactory = new ProxyFactory();
// 设置 TargetSource
proxyFactory.setTargetSource(targetSource);
// 创建代理对象
MyService proxy = (MyService) proxyFactory.getProxy();
// 调用代理对象的方法
proxy.doSomething();
}
}
# 7、定义新的通知类型
在实际开发中,可能需要自定义一些特殊的通知类型。org.springframework.aop.framework.adapter
包的主要作用就是让开发者能够在不修改 Spring AOP 核心框架的前提下,添加对新的自定义通知类型的支持。
# 7.1、AdvisorAdapter
接口
该接口定义了将特定类型的通知适配为 MethodInterceptor
的方法。Spring AOP 内部使用 MethodInterceptor
来处理通知逻辑,因此自定义的通知类型需要通过 AdvisorAdapter
转换为 MethodInterceptor
才能被框架识别和处理。AdvisorAdapter
接口包含以下两个方法:
boolean supportsAdvice(Advice advice)
:用于判断该适配器是否支持特定类型的通知。MethodInterceptor getInterceptor(Advisor advisor)
:将通知转换为MethodInterceptor
。
# 7.2、具体的适配器实现类
Spring 提供了一些默认的适配器实现类,用于支持现有的通知类型,例如:
MethodBeforeAdviceAdapter
:将MethodBeforeAdvice
适配为MethodInterceptor
。AfterReturningAdviceAdapter
:将AfterReturningAdvice
适配为MethodInterceptor
。ThrowsAdviceAdapter
:将AfterThrowingAdvice
适配为MethodInterceptor
。
# 7.3、AdvisorAdapterRegistry
接口及其实现类
AdvisorAdapterRegistry
接口定义了管理 AdvisorAdapter
的方法,如注册适配器、获取适配器等。DefaultAdvisorAdapterRegistry
是该接口的默认实现类,它负责维护一个 AdvisorAdapter
列表,并在需要时查找合适的适配器来处理通知。
# 7.4、工作原理
当 Spring AOP 框架遇到一个新的通知类型时,会通过 AdvisorAdapterRegistry
查找能够处理该通知类型的 AdvisorAdapter
。如果找到合适的适配器,就会调用适配器的 getInterceptor
方法将通知转换为 MethodInterceptor
,然后将其加入到拦截链中进行处理。具体步骤如下:
- 注册适配器:在 Spring 容器启动时,
DefaultAdvisorAdapterRegistry
会注册默认的适配器实现类。 - 添加通知:开发者将自定义的通知添加到
Advisor
中。 - 查找适配器:当 Spring AOP 框架处理
Advisor
时,会通过AdvisorAdapterRegistry
查找支持该通知类型的适配器。 - 适配通知:如果找到合适的适配器,会调用其
getInterceptor
方法将通知转换为MethodInterceptor
。 - 执行通知:将转换后的
MethodInterceptor
加入到拦截链中,在目标方法执行时依次执行拦截器逻辑。
# 7.5、示例代码
以下是一个简单的示例,展示了如何自定义一个通知类型并实现相应的适配器:
import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.Advisor;
import org.springframework.aop.framework.adapter.AdvisorAdapter;
import org.springframework.aop.framework.adapter.DefaultAdvisorAdapterRegistry;
// 自定义通知类型
class CustomAdvice implements Advice {
public void doCustomLogic() {
System.out.println("执行自定义通知逻辑");
}
}
// 自定义适配器
class CustomAdvisorAdapter implements AdvisorAdapter {
@Override
public boolean supportsAdvice(Advice advice) {
return advice instanceof CustomAdvice;
}
@Override
public MethodInterceptor getInterceptor(Advisor advisor) {
CustomAdvice customAdvice = (CustomAdvice) advisor.getAdvice();
return new MethodInterceptor() {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
customAdvice.doCustomLogic();
return invocation.proceed();
}
};
}
}
// 测试代码
public class AdapterExample {
public static void main(String[] args) {
// 创建自定义通知
CustomAdvice customAdvice = new CustomAdvice();
// 创建 Advisor
org.springframework.aop.support.DefaultPointcutAdvisor advisor = new org.springframework.aop.support.DefaultPointcutAdvisor(customAdvice);
// 注册自定义适配器
DefaultAdvisorAdapterRegistry registry = new DefaultAdvisorAdapterRegistry();
registry.registerAdvisorAdapter(new CustomAdvisorAdapter());
// 获取拦截器
MethodInterceptor interceptor = registry.getInterceptors(advisor)[0];
// 模拟方法调用
org.aopalliance.intercept.MethodInvocation invocation = new org.springframework.aop.framework.ReflectiveMethodInvocation(null, null, null, null, null) {
@Override
public Object proceed() throws Throwable {
System.out.println("执行目标方法");
return null;
}
};
try {
interceptor.invoke(invocation);
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
}
在上述示例中,我们定义了一个自定义通知类型 CustomAdvice
,并实现了相应的适配器 CustomAdvisorAdapter
。通过将自定义适配器注册到 DefaultAdvisorAdapterRegistry
中,我们可以让 Spring AOP 框架支持该自定义通知类型。
# 六、解决 Spring AOP 自调用失效问题
在实际开发中,我们经常遇到这样的情况:同一个类中的方法相互调用,但被调用的方法使用了 AOP,结果发现 AOP 逻辑没有执行。这是 Spring AOP 的一个经典"陷阱",让我们深入理解并掌握解决方案。
# 问题分析:为什么自调用会失效?
根本原因
Spring AOP 基于代理模式工作。当我们从外部调用 Bean 的方法时,实际调用的是代理对象的方法,代理对象会执行 AOP 逻辑然后调用真实对象的方法。但是,当在类内部进行方法调用时,使用的是 this
引用,指向的是真实对象而不是代理对象,因此 AOP 逻辑被绕过了。
典型问题场景
@Service
public class UserService {
@Transactional
public void saveUser(User user) {
// 保存用户基本信息
userRepository.save(user);
// 内部调用 - AOP失效!
sendWelcomeEmail(user.getEmail());
}
@Async
@Retryable(value = EmailException.class, maxAttempts = 3)
public void sendWelcomeEmail(String email) {
// 发送欢迎邮件
emailService.sendWelcomeEmail(email);
}
}
在上面的例子中,sendWelcomeEmail
方法虽然标记了 @Async
和 @Retryable
,但从 saveUser
方法内部调用时,这些 AOP 功能都不会生效。
# 解决方案对比
方案一:获取代理对象自调用(推荐)
@Service
public class UserService {
@Transactional
public void saveUser(User user) {
userRepository.save(user);
// 通过代理对象调用,AOP生效
getCurrentProxy().sendWelcomeEmail(user.getEmail());
}
@Async
@Retryable(value = EmailException.class, maxAttempts = 3)
public void sendWelcomeEmail(String email) {
emailService.sendWelcomeEmail(email);
}
// 获取当前代理对象
private UserService getCurrentProxy() {
return (UserService) AopContext.currentProxy();
}
}
// 配置类中启用代理暴露
@EnableAspectJAutoProxy(exposeProxy = true)
@Configuration
public class AopConfig {
// 配置内容
}
方案二:通用工具类方式
// 通用AOP工具类
@Component
public class AopUtils {
@SuppressWarnings("unchecked")
public static <T> T getCurrentProxy(T target) {
try {
return (T) AopContext.currentProxy();
} catch (IllegalStateException e) {
// 没有代理时返回原对象
return target;
}
}
// 简化的静态方法
public static <T> T getSelf(T target) {
return getCurrentProxy(target);
}
}
// 使用示例
@Service
public class OrderService {
public void processOrder(Order order) {
// 处理订单逻辑
order.setStatus("PROCESSING");
orderRepository.save(order);
// 使用工具类获取代理对象
AopUtils.getSelf(this).sendOrderConfirmation(order.getCustomerEmail());
}
@Async
@EventListener
public void sendOrderConfirmation(String email) {
notificationService.sendOrderConfirmation(email);
}
}
方案三:依赖注入自身(Spring 4.3+)
@Service
public class ProductService {
// Spring 4.3+ 支持自注入
@Autowired
private ProductService self;
public void updateProduct(Product product) {
// 更新产品信息
productRepository.save(product);
// 通过注入的代理对象调用
self.clearProductCache(product.getId());
}
@CacheEvict(value = "products", key = "#productId")
public void clearProductCache(Long productId) {
System.out.println("清理产品缓存:" + productId);
}
}
方案四:ApplicationContext 获取 Bean
@Service
public class PaymentService implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
public void processPayment(Payment payment) {
// 处理支付逻辑
payment.setStatus("COMPLETED");
paymentRepository.save(payment);
// 通过ApplicationContext获取代理对象
PaymentService proxyService = applicationContext.getBean(PaymentService.class);
proxyService.sendPaymentNotification(payment.getUserId());
}
@Async
@Transactional
public void sendPaymentNotification(Long userId) {
User user = userService.findById(userId);
notificationService.sendPaymentSuccess(user.getEmail());
}
}
# 最佳实践建议
推荐做法
- 优先考虑重构:将需要 AOP 的方法分离到不同的服务类中
- 使用代理对象自调用:在必须自调用的场景下,获取代理对象进行调用
- 统一工具类:封装代理获取逻辑,提高代码复用性
配置要求
// Spring Boot 启动类配置
@SpringBootApplication
@EnableAspectJAutoProxy(exposeProxy = true, proxyTargetClass = true)
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
注意事项
exposeProxy = true
必须设置,否则无法获取代理对象proxyTargetClass = true
建议设置,确保使用 CGLIB 代理- 代理对象获取会有一定的性能开销,不要在高频调用的地方使用
- 考虑代码的可读性,过多的代理调用会让代码逻辑变复杂
# 完整示例:缓存和日志组合
@Service
public class ArticleService {
private static final Logger logger = LoggerFactory.getLogger(ArticleService.class);
public Article getArticleWithViews(Long articleId) {
// 获取文章基本信息
Article article = articleRepository.findById(articleId);
if (article != null) {
// 通过代理对象调用,确保缓存和日志AOP生效
ArticleService proxy = (ArticleService) AopContext.currentProxy();
// 增加浏览次数(带缓存更新)
proxy.incrementViewCount(articleId);
// 记录用户行为(异步日志)
proxy.logUserBehavior("VIEW_ARTICLE", articleId);
}
return article;
}
@CacheEvict(value = "article_cache", key = "#articleId")
@Transactional
public void incrementViewCount(Long articleId) {
articleRepository.incrementViewCount(articleId);
logger.info("文章浏览次数已更新:{}", articleId);
}
@Async
@EventListener
public void logUserBehavior(String action, Long articleId) {
BehaviorLog log = new BehaviorLog();
log.setAction(action);
log.setArticleId(articleId);
log.setTimestamp(System.currentTimeMillis());
behaviorLogRepository.save(log);
logger.info("用户行为已记录:{} - {}", action, articleId);
}
}
通过理解 AOP 代理机制和掌握这些解决方案,我们就能够在复杂的业务场景中正确地使用 Spring AOP,避免踩坑。
# 七、总结与最佳实践
通过本文的深入介绍,我们全面了解了 Spring AOP 这个强大的编程工具。从基础概念到实战应用,从原理解析到编程技巧,Spring AOP 为我们解决了很多实际开发中的痛点。
# 核心要点回顾
理论基础
- Spring AOP 基于动态代理实现,智能选择 JDK 动态代理或 CGLIB 代理
- 切面、通知、切点、连接点等概念构成了 AOP 的核心体系
- 五种通知类型各有特色,环绕通知功能最强大,前置通知使用最频繁
实战技巧
- 切点表达式是精确控制 AOP 作用范围的关键
- 参数绑定让切面逻辑更加智能和灵活
- 通知执行顺序通过
@Order
注解精确控制 - 编程式代理创建为复杂场景提供了解决方案
# 最佳实践建议
设计原则
- 单一职责:每个切面只关注一个横切关注点
- 最小侵入:切点表达式要精确,避免过度拦截
- 性能考虑:谨慎使用环绕通知,避免不必要的性能开销
- 异常处理:切面中的异常要妥善处理,避免影响主业务
常见应用场景
- 日志记录:方法调用跟踪、参数记录、执行时间统计
- 安全控制:权限检查、参数验证、操作审计
- 事务管理:声明式事务是 AOP 最成功的应用
- 缓存管理:自动缓存查询结果,提升系统性能
- 异常监控:统一异常处理、告警通知、错误统计
注意事项
- 同类方法调用不会触发 AOP,需要使用
AopContext.currentProxy()
final
类和方法无法被 CGLIB 代理- 切面的执行顺序很重要,合理设置优先级
- 过度使用 AOP 会让代码逻辑变得复杂,要适度
# 发展趋势
Spring AOP 作为一个成熟的技术,在未来的发展中将更加注重:
- 性能优化:更高效的代理创建和方法拦截
- 云原生支持:与微服务、容器化技术更好的集成
- 可观测性:与监控、链路追踪等技术的深度结合
- 开发体验:更简单的配置、更智能的错误提示
Spring AOP 不仅是一个技术工具,更是一种编程思想的体现。它教会我们如何优雅地处理横切关注点,如何让代码更加清晰和可维护。掌握好 Spring AOP,你的 Spring 技能将更上一层楼!
愿你在技术的道路上越走越远,代码越写越优雅!