Appearance
Spring AOP 详解
概述
AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,它允许开发者将横切关注点(cross-cutting concerns)从业务逻辑中分离出来。Spring AOP 是 Spring 框架的核心特性之一,它提供了声明式的方式来实现面向切面编程,主要用于处理系统中的横切关注点,如日志记录、事务管理、安全检查、性能监控等。
目录
AOP 基本概念
什么是面向切面编程
面向切面编程(AOP)是对面向对象编程(OOP)的补充,它通过预编译方式和运行期动态代理实现程序功能的统一维护。AOP 能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度。
横切关注点
横切关注点是指那些影响多个类的公共行为,这些行为通常难以通过传统的面向对象方式进行模块化。常见的横切关注点包括:
- 日志记录:记录方法调用、参数、返回值等
- 事务管理:数据库事务的开启、提交、回滚
- 安全检查:权限验证、身份认证
- 性能监控:方法执行时间统计
- 缓存管理:缓存的读取、更新、清除
- 异常处理:统一的异常处理逻辑
传统方式 vs AOP 方式
传统方式:
java
public class UserService {
private static final Logger logger = LoggerFactory.getLogger(UserService.class);
public User createUser(User user) {
// 日志记录
logger.info("开始创建用户: {}", user.getUsername());
// 参数验证
if (user == null || user.getUsername() == null) {
throw new IllegalArgumentException("用户信息不能为空");
}
// 性能监控
long startTime = System.currentTimeMillis();
try {
// 核心业务逻辑
User savedUser = userRepository.save(user);
// 日志记录
logger.info("用户创建成功: {}", savedUser.getId());
return savedUser;
} catch (Exception e) {
// 异常处理
logger.error("用户创建失败", e);
throw e;
} finally {
// 性能监控
long endTime = System.currentTimeMillis();
logger.info("方法执行时间: {}ms", endTime - startTime);
}
}
}
AOP 方式:
java
@Service
public class UserService {
@Loggable
@PerformanceMonitor
@ValidateParams
public User createUser(User user) {
// 只关注核心业务逻辑
return userRepository.save(user);
}
}
// 日志切面
@Aspect
@Component
public class LoggingAspect {
@Around("@annotation(Loggable)")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
// 日志逻辑
}
}
AOP 核心术语
1. 切面(Aspect)
切面是横切关注点的模块化,它将散布在多个类中的公共行为封装到一个可重用的模块中。
java
@Aspect
@Component
public class LoggingAspect {
// 切面定义
}
2. 连接点(Join Point)
连接点是程序执行过程中能够应用通知的所有点,如方法调用、异常抛出等。在 Spring AOP 中,连接点总是方法的执行。
java
// 这些都是连接点
public class UserService {
public User findById(Long id) { ... } // 连接点
public User save(User user) { ... } // 连接点
public void delete(Long id) { ... } // 连接点
}
3. 切点(Pointcut)
切点是连接点的集合,它定义了通知应该在何处执行。
java
@Pointcut("execution(* com.example.service.*.*(..))") // 切点表达式
public void serviceLayer() {}
@Pointcut("@annotation(org.springframework.transaction.annotation.Transactional)")
public void transactionalMethods() {}
4. 通知(Advice)
通知是切面在特定连接点执行的动作。Spring AOP 支持五种类型的通知:
- 前置通知(Before):在连接点之前执行
- 后置通知(After):在连接点之后执行
- 返回通知(AfterReturning):在连接点正常返回后执行
- 异常通知(AfterThrowing):在连接点抛出异常后执行
- 环绕通知(Around):围绕连接点执行
5. 目标对象(Target Object)
目标对象是被一个或多个切面通知的对象,也被称为被通知对象。
java
@Service
public class UserService { // 这是目标对象
public User findById(Long id) {
return userRepository.findById(id);
}
}
6. AOP 代理(AOP Proxy)
AOP 代理是 AOP 框架创建的对象,用于实现切面契约。在 Spring 中,AOP 代理可以是 JDK 动态代理或 CGLIB 代理。
7. 织入(Weaving)
织入是将切面与目标对象连接起来创建通知对象的过程。Spring AOP 在运行时进行织入。
Spring AOP 实现原理
代理模式
Spring AOP 基于代理模式实现,它在运行时创建目标对象的代理,在代理中织入切面逻辑。
java
// 目标接口
public interface UserService {
User findById(Long id);
User save(User user);
}
// 目标实现
@Service
public class UserServiceImpl implements UserService {
@Override
public User findById(Long id) {
return userRepository.findById(id);
}
@Override
public User save(User user) {
return userRepository.save(user);
}
}
// Spring 创建的代理对象(概念示意)
public class UserServiceProxy implements UserService {
private UserService target;
private List<Advisor> advisors;
@Override
public User findById(Long id) {
// 前置通知
for (Advisor advisor : advisors) {
advisor.getAdvice().before();
}
try {
// 调用目标方法
User result = target.findById(id);
// 返回通知
for (Advisor advisor : advisors) {
advisor.getAdvice().afterReturning(result);
}
return result;
} catch (Exception e) {
// 异常通知
for (Advisor advisor : advisors) {
advisor.getAdvice().afterThrowing(e);
}
throw e;
} finally {
// 后置通知
for (Advisor advisor : advisors) {
advisor.getAdvice().after();
}
}
}
}
两种代理方式
1. JDK 动态代理
java
// 当目标对象实现了接口时,Spring 默认使用 JDK 动态代理
public interface UserService {
User findById(Long id);
}
@Service
public class UserServiceImpl implements UserService {
@Override
public User findById(Long id) {
return userRepository.findById(id);
}
}
2. CGLIB 代理
java
// 当目标对象没有实现接口时,Spring 使用 CGLIB 代理
@Service
public class UserService { // 没有实现接口
public User findById(Long id) {
return userRepository.findById(id);
}
}
// 强制使用 CGLIB 代理
@EnableAspectJAutoProxy(proxyTargetClass = true)
@Configuration
public class AopConfig {
}
切点表达式
表达式语法
Spring AOP 使用 AspectJ 的切点表达式语言来定义切点。
1. execution 表达式
java
// 语法:execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
@Pointcut("execution(public * com.example.service.*.*(..))") // 所有 service 包下的 public 方法
public void publicServiceMethods() {}
@Pointcut("execution(* com.example.service.UserService.find*(..))")
public void userServiceFindMethods() {} // UserService 中以 find 开头的方法
@Pointcut("execution(User com.example.service.UserService.*(..))")
public void userServiceReturningUser() {} // 返回 User 类型的方法
@Pointcut("execution(* com.example.service.*.*(Long, ..))")
public void methodsWithLongFirstParam() {} // 第一个参数为 Long 类型的方法
2. within 表达式
java
@Pointcut("within(com.example.service.*)") // service 包下的所有类
public void servicePackage() {}
@Pointcut("within(com.example.service..*)") // service 包及其子包下的所有类
public void servicePackageAndSubpackages() {}
@Pointcut("within(com.example.service.UserService)") // 特定类
public void userServiceClass() {}
3. this 和 target 表达式
java
@Pointcut("this(com.example.service.UserService)") // 代理对象是 UserService 类型
public void thisUserService() {}
@Pointcut("target(com.example.service.UserService)") // 目标对象是 UserService 类型
public void targetUserService() {}
4. args 表达式
java
@Pointcut("args(Long)") // 只有一个 Long 类型参数的方法
public void singleLongArg() {}
@Pointcut("args(Long, String)") // 两个参数:Long 和 String
public void longAndStringArgs() {}
@Pointcut("args(Long, ..)") // 第一个参数是 Long,后面可以有任意参数
public void longFirstArg() {}
5. 注解表达式
java
@Pointcut("@annotation(org.springframework.transaction.annotation.Transactional)")
public void transactionalMethods() {} // 标注了 @Transactional 的方法
@Pointcut("@within(org.springframework.stereotype.Service)")
public void serviceClasses() {} // 标注了 @Service 的类中的所有方法
@Pointcut("@target(org.springframework.stereotype.Repository)")
public void repositoryBeans() {} // 目标对象的类标注了 @Repository
@Pointcut("@args(com.example.annotation.Validated)")
public void validatedArgs() {} // 参数标注了 @Validated
组合表达式
java
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceLayer() {}
@Pointcut("execution(* com.example.repository.*.*(..))")
public void repositoryLayer() {}
@Pointcut("serviceLayer() || repositoryLayer()") // 或操作
public void serviceOrRepository() {}
@Pointcut("serviceLayer() && @annotation(org.springframework.transaction.annotation.Transactional)")
public void transactionalServiceMethods() {} // 与操作
@Pointcut("serviceLayer() && !execution(* com.example.service.*.get*(..))")
public void nonGetterServiceMethods() {} // 非操作
通知类型
1. 前置通知(@Before)
在目标方法执行之前执行。
java
@Aspect
@Component
public class LoggingAspect {
@Before("execution(* com.example.service.*.*(..))")
public void logBefore(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
logger.info("方法 {} 开始执行,参数:{}", methodName, Arrays.toString(args));
}
@Before("@annotation(loggable)")
public void logBeforeAnnotated(JoinPoint joinPoint, Loggable loggable) {
logger.info("执行标注了 @Loggable 的方法:{}", joinPoint.getSignature().getName());
}
}
2. 后置通知(@After)
在目标方法执行之后执行,无论方法是否抛出异常。
java
@Aspect
@Component
public class CleanupAspect {
@After("execution(* com.example.service.*.*(..))")
public void cleanup(JoinPoint joinPoint) {
logger.info("方法 {} 执行完成,进行清理工作", joinPoint.getSignature().getName());
// 清理资源、重置状态等
}
}
3. 返回通知(@AfterReturning)
在目标方法正常返回后执行。
java
@Aspect
@Component
public class AuditAspect {
@AfterReturning(pointcut = "execution(* com.example.service.UserService.save(..))",
returning = "result")
public void auditUserSave(JoinPoint joinPoint, User result) {
logger.info("用户保存成功:{}", result.getId());
// 记录审计日志
auditService.recordUserCreation(result);
}
@AfterReturning(pointcut = "@annotation(Cacheable)", returning = "result")
public void cacheResult(JoinPoint joinPoint, Object result) {
String key = generateCacheKey(joinPoint);
cacheManager.put(key, result);
}
}
4. 异常通知(@AfterThrowing)
在目标方法抛出异常后执行。
java
@Aspect
@Component
public class ExceptionHandlingAspect {
@AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))",
throwing = "ex")
public void handleException(JoinPoint joinPoint, Exception ex) {
String methodName = joinPoint.getSignature().getName();
logger.error("方法 {} 执行异常", methodName, ex);
// 发送告警通知
alertService.sendAlert("方法执行异常: " + methodName, ex);
}
@AfterThrowing(pointcut = "@annotation(Retryable)", throwing = "ex")
public void handleRetryableException(JoinPoint joinPoint, Exception ex) {
// 处理可重试的异常
retryService.scheduleRetry(joinPoint, ex);
}
}
5. 环绕通知(@Around)
围绕目标方法执行,是最强大的通知类型。
java
@Aspect
@Component
public class PerformanceAspect {
@Around("execution(* com.example.service.*.*(..))")
public Object measureExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
String methodName = joinPoint.getSignature().getName();
try {
logger.info("方法 {} 开始执行", methodName);
// 执行目标方法
Object result = joinPoint.proceed();
long endTime = System.currentTimeMillis();
logger.info("方法 {} 执行成功,耗时:{}ms", methodName, endTime - startTime);
return result;
} catch (Exception e) {
long endTime = System.currentTimeMillis();
logger.error("方法 {} 执行失败,耗时:{}ms", methodName, endTime - startTime, e);
throw e;
}
}
@Around("@annotation(rateLimited)")
public Object rateLimit(ProceedingJoinPoint joinPoint, RateLimited rateLimited) throws Throwable {
String key = generateRateLimitKey(joinPoint);
if (rateLimiter.tryAcquire(key, rateLimited.value())) {
return joinPoint.proceed();
} else {
throw new RateLimitExceededException("请求过于频繁");
}
}
}
通知的执行顺序
java
@Aspect
@Component
@Order(1) // 设置切面执行顺序
public class FirstAspect {
@Around("execution(* com.example.service.*.*(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("First - Around Before");
try {
Object result = joinPoint.proceed();
System.out.println("First - Around After Returning");
return result;
} catch (Exception e) {
System.out.println("First - Around After Throwing");
throw e;
} finally {
System.out.println("First - Around After (finally)");
}
}
}
@Aspect
@Component
@Order(2)
public class SecondAspect {
@Before("execution(* com.example.service.*.*(..))")
public void before() {
System.out.println("Second - Before");
}
@After("execution(* com.example.service.*.*(..))")
public void after() {
System.out.println("Second - After");
}
}
// 执行顺序:
// First - Around Before
// Second - Before
// [目标方法执行]
// Second - After
// First - Around After Returning
// First - Around After (finally)
切面定义
基本切面
java
@Aspect
@Component
public class BasicAspect {
private static final Logger logger = LoggerFactory.getLogger(BasicAspect.class);
// 定义切点
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceLayer() {}
// 前置通知
@Before("serviceLayer()")
public void logBefore(JoinPoint joinPoint) {
logger.info("执行方法:{}", joinPoint.getSignature().getName());
}
// 环绕通知
@Around("serviceLayer()")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
try {
Object result = joinPoint.proceed();
long endTime = System.currentTimeMillis();
logger.info("方法执行成功,耗时:{}ms", endTime - startTime);
return result;
} catch (Exception e) {
logger.error("方法执行异常", e);
throw e;
}
}
}
参数化切面
java
@Aspect
@Component
public class ParameterizedAspect {
// 获取方法参数
@Before("execution(* com.example.service.UserService.*(..)) && args(id,..)")
public void logUserId(Long id) {
logger.info("操作用户ID:{}", id);
}
// 获取注解参数
@Around("@annotation(cacheable)")
public Object handleCacheable(ProceedingJoinPoint joinPoint, Cacheable cacheable) throws Throwable {
String cacheName = cacheable.value();
String key = generateKey(joinPoint);
// 先查缓存
Object cached = cacheManager.get(cacheName, key);
if (cached != null) {
return cached;
}
// 执行方法并缓存结果
Object result = joinPoint.proceed();
cacheManager.put(cacheName, key, result);
return result;
}
// 获取返回值
@AfterReturning(pointcut = "execution(* com.example.service.UserService.save(..))",
returning = "user")
public void auditUserSave(User user) {
auditService.recordUserCreation(user.getId(), user.getUsername());
}
}
条件切面
java
@Aspect
@Component
@ConditionalOnProperty(name = "app.aop.logging.enabled", havingValue = "true")
public class ConditionalLoggingAspect {
@Around("@annotation(Loggable)")
public Object logExecution(ProceedingJoinPoint joinPoint) throws Throwable {
// 只有在配置启用时才会生效
return joinPoint.proceed();
}
}
@Aspect
@Component
@Profile("!production") // 非生产环境才启用
public class DebugAspect {
@Before("execution(* com.example.service.*.*(..))")
public void debugLog(JoinPoint joinPoint) {
logger.debug("调试信息:{}", joinPoint.getSignature());
}
}
代理机制
JDK 动态代理 vs CGLIB 代理
JDK 动态代理
java
// 接口
public interface UserService {
User findById(Long id);
User save(User user);
}
// 实现类
@Service
public class UserServiceImpl implements UserService {
@Override
public User findById(Long id) {
return userRepository.findById(id);
}
@Override
public User save(User user) {
return userRepository.save(user);
}
}
// Spring 会创建 JDK 动态代理
@Autowired
private UserService userService; // 这里注入的是代理对象
CGLIB 代理
java
// 没有接口的类
@Service
public class UserService {
public User findById(Long id) {
return userRepository.findById(id);
}
public User save(User user) {
return userRepository.save(user);
}
}
// Spring 会创建 CGLIB 代理
@Autowired
private UserService userService; // 这里注入的是 CGLIB 代理对象
代理的限制
1. 内部方法调用
java
@Service
public class UserService {
@Transactional
public User saveUser(User user) {
// 这个调用不会触发 AOP,因为是内部调用
return this.doSave(user);
}
@Transactional
public User doSave(User user) {
return userRepository.save(user);
}
}
// 解决方案1:注入自己
@Service
public class UserService {
@Autowired
private UserService self; // 注入代理对象
@Transactional
public User saveUser(User user) {
// 通过代理调用,会触发 AOP
return self.doSave(user);
}
@Transactional
public User doSave(User user) {
return userRepository.save(user);
}
}
// 解决方案2:使用 AopContext
@Service
@EnableAspectJAutoProxy(exposeProxy = true)
public class UserService {
@Transactional
public User saveUser(User user) {
UserService proxy = (UserService) AopContext.currentProxy();
return proxy.doSave(user);
}
@Transactional
public User doSave(User user) {
return userRepository.save(user);
}
}
2. final 方法和类
java
// CGLIB 无法代理 final 类
public final class FinalUserService {
public User findById(Long id) {
return userRepository.findById(id);
}
}
// CGLIB 无法代理 final 方法
@Service
public class UserService {
public final User findById(Long id) { // final 方法不会被代理
return userRepository.findById(id);
}
public User save(User user) { // 普通方法会被代理
return userRepository.save(user);
}
}
强制使用 CGLIB
java
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true) // 强制使用 CGLIB
public class AopConfig {
}
// 或者在 application.properties 中配置
// spring.aop.proxy-target-class=true
AOP 配置方式
1. 注解配置(推荐)
java
@Configuration
@EnableAspectJAutoProxy // 启用 AspectJ 自动代理
@ComponentScan(basePackages = "com.example")
public class AopConfig {
}
@Aspect
@Component
public class LoggingAspect {
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceLayer() {}
@Around("serviceLayer()")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
// 切面逻辑
return joinPoint.proceed();
}
}
2. XML 配置
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"
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">
<!-- 启用 AspectJ 自动代理 -->
<aop:aspectj-autoproxy/>
<!-- 定义切面 -->
<bean id="loggingAspect" class="com.example.aspect.LoggingAspect"/>
<!-- AOP 配置 -->
<aop:config>
<aop:aspect ref="loggingAspect">
<aop:pointcut id="serviceLayer"
expression="execution(* com.example.service.*.*(..))"/>
<aop:before pointcut-ref="serviceLayer" method="logBefore"/>
<aop:after pointcut-ref="serviceLayer" method="logAfter"/>
<aop:around pointcut-ref="serviceLayer" method="logAround"/>
</aop:aspect>
</aop:config>
</beans>
3. Java 配置
java
@Configuration
@EnableAspectJAutoProxy
public class AopConfig {
@Bean
public LoggingAspect loggingAspect() {
return new LoggingAspect();
}
@Bean
public PerformanceAspect performanceAspect() {
return new PerformanceAspect();
}
@Bean
public DefaultPointcutAdvisor loggingAdvisor() {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("execution(* com.example.service.*.*(..))");
DefaultPointcutAdvisor advisor = new DefaultPointcutAdvisor();
advisor.setPointcut(pointcut);
advisor.setAdvice(new LoggingInterceptor());
return advisor;
}
}
实际应用场景
1. 日志记录
java
@Aspect
@Component
public class LoggingAspect {
private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);
@Around("@annotation(Loggable)")
public Object logMethodExecution(ProceedingJoinPoint joinPoint) throws Throwable {
String className = joinPoint.getTarget().getClass().getSimpleName();
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
logger.info("[{}] 方法 {} 开始执行,参数:{}", className, methodName, Arrays.toString(args));
long startTime = System.currentTimeMillis();
try {
Object result = joinPoint.proceed();
long endTime = System.currentTimeMillis();
logger.info("[{}] 方法 {} 执行成功,耗时:{}ms,返回值:{}",
className, methodName, endTime - startTime, result);
return result;
} catch (Exception e) {
long endTime = System.currentTimeMillis();
logger.error("[{}] 方法 {} 执行失败,耗时:{}ms,异常:{}",
className, methodName, endTime - startTime, e.getMessage(), e);
throw e;
}
}
}
// 使用
@Service
public class UserService {
@Loggable
public User createUser(User user) {
return userRepository.save(user);
}
}
2. 性能监控
java
@Aspect
@Component
public class PerformanceMonitoringAspect {
private final MeterRegistry meterRegistry;
public PerformanceMonitoringAspect(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
}
@Around("@annotation(PerformanceMonitor)")
public Object monitorPerformance(ProceedingJoinPoint joinPoint) throws Throwable {
String methodName = joinPoint.getSignature().getName();
String className = joinPoint.getTarget().getClass().getSimpleName();
Timer.Sample sample = Timer.start(meterRegistry);
try {
Object result = joinPoint.proceed();
// 记录成功执行的指标
sample.stop(Timer.builder("method.execution.time")
.tag("class", className)
.tag("method", methodName)
.tag("status", "success")
.register(meterRegistry));
// 增加成功计数
Counter.builder("method.execution.count")
.tag("class", className)
.tag("method", methodName)
.tag("status", "success")
.register(meterRegistry)
.increment();
return result;
} catch (Exception e) {
// 记录失败执行的指标
sample.stop(Timer.builder("method.execution.time")
.tag("class", className)
.tag("method", methodName)
.tag("status", "error")
.register(meterRegistry));
// 增加失败计数
Counter.builder("method.execution.count")
.tag("class", className)
.tag("method", methodName)
.tag("status", "error")
.tag("exception", e.getClass().getSimpleName())
.register(meterRegistry)
.increment();
throw e;
}
}
}
3. 缓存管理
java
@Aspect
@Component
public class CacheAspect {
private final CacheManager cacheManager;
public CacheAspect(CacheManager cacheManager) {
this.cacheManager = cacheManager;
}
@Around("@annotation(cacheable)")
public Object handleCacheable(ProceedingJoinPoint joinPoint, Cacheable cacheable) throws Throwable {
String cacheName = cacheable.value();
String key = generateCacheKey(joinPoint, cacheable.key());
// 查询缓存
Cache cache = cacheManager.getCache(cacheName);
if (cache != null) {
Cache.ValueWrapper wrapper = cache.get(key);
if (wrapper != null) {
logger.debug("缓存命中:{}", key);
return wrapper.get();
}
}
// 执行方法
Object result = joinPoint.proceed();
// 缓存结果
if (cache != null && result != null) {
cache.put(key, result);
logger.debug("缓存更新:{}", key);
}
return result;
}
@AfterReturning(pointcut = "@annotation(cacheEvict)", returning = "result")
public void handleCacheEvict(JoinPoint joinPoint, CacheEvict cacheEvict, Object result) {
String cacheName = cacheEvict.value();
if (cacheEvict.allEntries()) {
// 清空整个缓存
Cache cache = cacheManager.getCache(cacheName);
if (cache != null) {
cache.clear();
logger.debug("清空缓存:{}", cacheName);
}
} else {
// 清除特定缓存项
String key = generateCacheKey(joinPoint, cacheEvict.key());
Cache cache = cacheManager.getCache(cacheName);
if (cache != null) {
cache.evict(key);
logger.debug("清除缓存:{}", key);
}
}
}
private String generateCacheKey(JoinPoint joinPoint, String keyExpression) {
if (StringUtils.hasText(keyExpression)) {
// 使用 SpEL 表达式解析 key
return spelExpressionParser.parseExpression(keyExpression)
.getValue(new MethodBasedEvaluationContext(joinPoint), String.class);
} else {
// 默认使用方法名和参数生成 key
return joinPoint.getSignature().getName() + "_" +
Arrays.hashCode(joinPoint.getArgs());
}
}
}
4. 安全检查
java
@Aspect
@Component
public class SecurityAspect {
private final AuthenticationService authenticationService;
private final AuthorizationService authorizationService;
@Before("@annotation(requiresAuth)")
public void checkAuthentication(JoinPoint joinPoint, RequiresAuth requiresAuth) {
// 检查用户是否已认证
if (!authenticationService.isAuthenticated()) {
throw new AuthenticationException("用户未认证");
}
// 检查用户角色
String[] requiredRoles = requiresAuth.roles();
if (requiredRoles.length > 0) {
boolean hasRole = authorizationService.hasAnyRole(requiredRoles);
if (!hasRole) {
throw new AuthorizationException("用户权限不足");
}
}
// 检查用户权限
String[] requiredPermissions = requiresAuth.permissions();
if (requiredPermissions.length > 0) {
boolean hasPermission = authorizationService.hasAnyPermission(requiredPermissions);
if (!hasPermission) {
throw new AuthorizationException("用户权限不足");
}
}
}
@Around("@annotation(rateLimited)")
public Object rateLimit(ProceedingJoinPoint joinPoint, RateLimited rateLimited) throws Throwable {
String userId = authenticationService.getCurrentUserId();
String methodName = joinPoint.getSignature().getName();
String key = "rate_limit:" + userId + ":" + methodName;
// 检查速率限制
if (!rateLimiter.tryAcquire(key, rateLimited.value(), rateLimited.timeUnit())) {
throw new RateLimitExceededException("请求过于频繁,请稍后再试");
}
return joinPoint.proceed();
}
}
// 使用
@Service
public class UserService {
@RequiresAuth(roles = {"ADMIN", "USER_MANAGER"})
public User createUser(User user) {
return userRepository.save(user);
}
@RateLimited(value = 10, timeUnit = TimeUnit.MINUTES)
public List<User> searchUsers(String keyword) {
return userRepository.findByKeyword(keyword);
}
}
5. 事务管理
java
@Aspect
@Component
public class TransactionAspect {
private final PlatformTransactionManager transactionManager;
@Around("@annotation(transactional)")
public Object handleTransaction(ProceedingJoinPoint joinPoint, Transactional transactional) throws Throwable {
TransactionDefinition definition = new DefaultTransactionDefinition(
transactional.propagation().value());
TransactionStatus status = transactionManager.getTransaction(definition);
try {
Object result = joinPoint.proceed();
transactionManager.commit(status);
return result;
} catch (Exception e) {
// 检查是否需要回滚
boolean shouldRollback = shouldRollback(e, transactional.rollbackFor(),
transactional.noRollbackFor());
if (shouldRollback) {
transactionManager.rollback(status);
} else {
transactionManager.commit(status);
}
throw e;
}
}
private boolean shouldRollback(Exception e, Class<? extends Throwable>[] rollbackFor,
Class<? extends Throwable>[] noRollbackFor) {
// 实现回滚逻辑判断
return true;
}
}
高级特性
1. 引入(Introduction)
引入允许为现有的类添加新的方法和属性。
java
// 定义要引入的接口
public interface Auditable {
void setCreatedDate(Date date);
Date getCreatedDate();
void setLastModified(Date date);
Date getLastModified();
}
// 引入的实现
public class AuditableImpl implements Auditable {
private Date createdDate;
private Date lastModified;
@Override
public void setCreatedDate(Date date) {
this.createdDate = date;
}
@Override
public Date getCreatedDate() {
return createdDate;
}
@Override
public void setLastModified(Date date) {
this.lastModified = date;
}
@Override
public Date getLastModified() {
return lastModified;
}
}
// 切面定义引入
@Aspect
@Component
public class AuditAspect {
@DeclareParents(value = "com.example.domain.*", defaultImpl = AuditableImpl.class)
public static Auditable auditable;
@Before("execution(* com.example.repository.*.save(..)) && this(auditable)")
public void auditSave(JoinPoint joinPoint, Auditable auditable) {
Date now = new Date();
if (auditable.getCreatedDate() == null) {
auditable.setCreatedDate(now);
}
auditable.setLastModified(now);
}
}
// 使用
@Entity
public class User {
private Long id;
private String username;
// 其他字段
}
// 在运行时,User 对象可以转换为 Auditable
User user = userService.findById(1L);
Auditable auditableUser = (Auditable) user;
Date createdDate = auditableUser.getCreatedDate();
2. 切面继承
java
// 基础切面
@Aspect
public abstract class BaseLoggingAspect {
protected final Logger logger = LoggerFactory.getLogger(getClass());
@Around("loggingPointcut()")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
String methodName = joinPoint.getSignature().getName();
logger.info("方法 {} 开始执行", methodName);
try {
Object result = joinPoint.proceed();
logger.info("方法 {} 执行成功", methodName);
return result;
} catch (Exception e) {
logger.error("方法 {} 执行失败", methodName, e);
throw e;
}
}
// 抽象切点,由子类实现
@Pointcut
public abstract void loggingPointcut();
}
// 服务层日志切面
@Aspect
@Component
public class ServiceLoggingAspect extends BaseLoggingAspect {
@Override
@Pointcut("execution(* com.example.service.*.*(..))")
public void loggingPointcut() {
}
}
// 控制器层日志切面
@Aspect
@Component
public class ControllerLoggingAspect extends BaseLoggingAspect {
@Override
@Pointcut("execution(* com.example.controller.*.*(..))")
public void loggingPointcut() {
}
}
3. 动态切面
java
@Component
public class DynamicAspectRegistry {
private final List<DynamicAspect> aspects = new ArrayList<>();
public void registerAspect(DynamicAspect aspect) {
aspects.add(aspect);
}
public void unregisterAspect(DynamicAspect aspect) {
aspects.remove(aspect);
}
@EventListener
public void handleConfigChange(ConfigChangeEvent event) {
// 根据配置变化动态注册或注销切面
if (event.isLoggingEnabled()) {
registerAspect(new LoggingAspect());
} else {
unregisterAspect(findAspectByType(LoggingAspect.class));
}
}
}
@Aspect
@Component
public class DynamicExecutionAspect {
@Autowired
private DynamicAspectRegistry aspectRegistry;
@Around("execution(* com.example.service.*.*(..))")
public Object dynamicExecution(ProceedingJoinPoint joinPoint) throws Throwable {
// 动态执行注册的切面
for (DynamicAspect aspect : aspectRegistry.getAspects()) {
if (aspect.matches(joinPoint)) {
return aspect.execute(joinPoint);
}
}
return joinPoint.proceed();
}
}
性能考虑
1. 切点表达式优化
java
// 低效:过于宽泛的切点
@Pointcut("execution(* *.*(..))")
public void allMethods() {} // 会匹配所有方法,性能很差
// 高效:精确的切点
@Pointcut("execution(* com.example.service.*Service.*(..))")
public void serviceMethods() {} // 只匹配特定包下的 Service 类
// 高效:使用注解切点
@Pointcut("@annotation(org.springframework.transaction.annotation.Transactional)")
public void transactionalMethods() {} // 只匹配标注了注解的方法
// 高效:组合使用
@Pointcut("within(com.example.service..*) && @annotation(Loggable)")
public void loggableServiceMethods() {} // 精确匹配
2. 避免过度使用 AOP
java
// 不推荐:为简单逻辑使用 AOP
@Aspect
@Component
public class SimpleValidationAspect {
@Before("execution(* com.example.service.*.*(..))")
public void validateNotNull(JoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
for (Object arg : args) {
if (arg == null) {
throw new IllegalArgumentException("参数不能为空");
}
}
}
}
// 推荐:直接在方法中处理
@Service
public class UserService {
public User createUser(User user) {
Assert.notNull(user, "用户信息不能为空");
return userRepository.save(user);
}
}
3. 缓存切点匹配结果
java
@Aspect
@Component
public class CachedPointcutAspect {
private final Map<Method, Boolean> pointcutCache = new ConcurrentHashMap<>();
@Around("execution(* com.example.service.*.*(..))")
public Object cachedPointcutExecution(ProceedingJoinPoint joinPoint) throws Throwable {
Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
// 缓存切点匹配结果
Boolean shouldApply = pointcutCache.computeIfAbsent(method, this::shouldApplyAdvice);
if (shouldApply) {
// 应用通知逻辑
return applyAdvice(joinPoint);
} else {
return joinPoint.proceed();
}
}
private Boolean shouldApplyAdvice(Method method) {
// 复杂的匹配逻辑
return method.isAnnotationPresent(Loggable.class);
}
private Object applyAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
// 通知逻辑
return joinPoint.proceed();
}
}
4. 异步处理
java
@Aspect
@Component
public class AsyncLoggingAspect {
@Async
@AfterReturning("execution(* com.example.service.*.*(..))")
public void logAsync(JoinPoint joinPoint) {
// 异步记录日志,不影响主流程性能
String methodName = joinPoint.getSignature().getName();
logger.info("异步记录:方法 {} 执行完成", methodName);
}
}
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean
public TaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(5);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("async-logging-");
executor.initialize();
return executor;
}
}
最佳实践
1. 切面设计原则
java
// 单一职责:每个切面只处理一种横切关注点
@Aspect
@Component
public class LoggingAspect {
// 只处理日志记录
}
@Aspect
@Component
public class SecurityAspect {
// 只处理安全检查
}
@Aspect
@Component
public class PerformanceAspect {
// 只处理性能监控
}
2. 切点表达式最佳实践
java
@Aspect
@Component
public class BestPracticeAspect {
// 推荐:使用具体的包名
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceLayer() {}
// 推荐:使用注解切点
@Pointcut("@annotation(Loggable)")
public void loggableMethods() {}
// 推荐:组合使用提高精确度
@Pointcut("serviceLayer() && @annotation(Transactional)")
public void transactionalServiceMethods() {}
// 避免:过于宽泛的表达式
// @Pointcut("execution(* *.*(..))")
// 避免:复杂的表达式
// @Pointcut("execution(* com.example..*.*(..)) && !execution(* com.example.util..*.*(..)) && args(String, ..)")
}
3. 异常处理
java
@Aspect
@Component
public class RobustAspect {
@Around("execution(* com.example.service.*.*(..))")
public Object robustExecution(ProceedingJoinPoint joinPoint) throws Throwable {
try {
// 前置处理
preProcess(joinPoint);
Object result = joinPoint.proceed();
// 后置处理
postProcess(joinPoint, result);
return result;
} catch (Exception e) {
// 异常处理
handleException(joinPoint, e);
throw e;
}
}
private void preProcess(JoinPoint joinPoint) {
try {
// 前置处理逻辑
} catch (Exception e) {
// 记录日志,但不影响主流程
logger.warn("前置处理失败", e);
}
}
private void postProcess(JoinPoint joinPoint, Object result) {
try {
// 后置处理逻辑
} catch (Exception e) {
// 记录日志,但不影响主流程
logger.warn("后置处理失败", e);
}
}
private void handleException(JoinPoint joinPoint, Exception e) {
try {
// 异常处理逻辑
} catch (Exception ex) {
// 记录日志,但不抛出新异常
logger.error("异常处理失败", ex);
}
}
}
4. 测试友好的设计
java
@Aspect
@Component
public class TestableAspect {
private final LoggingService loggingService;
// 使用依赖注入,便于测试
public TestableAspect(LoggingService loggingService) {
this.loggingService = loggingService;
}
@Around("@annotation(Loggable)")
public Object logExecution(ProceedingJoinPoint joinPoint) throws Throwable {
return loggingService.logMethodExecution(joinPoint);
}
}
// 测试类
@ExtendWith(MockitoExtension.class)
class TestableAspectTest {
@Mock
private LoggingService loggingService;
@InjectMocks
private TestableAspect aspect;
@Test
void shouldLogMethodExecution() throws Throwable {
// 测试切面逻辑
}
}
5. 配置管理
java
@Aspect
@Component
@ConditionalOnProperty(name = "app.aop.enabled", havingValue = "true", matchIfMissing = true)
public class ConfigurableAspect {
@Value("${app.aop.logging.enabled:true}")
private boolean loggingEnabled;
@Value("${app.aop.performance.enabled:false}")
private boolean performanceEnabled;
@Around("execution(* com.example.service.*.*(..))")
public Object conditionalExecution(ProceedingJoinPoint joinPoint) throws Throwable {
if (loggingEnabled) {
logMethodExecution(joinPoint);
}
long startTime = performanceEnabled ? System.currentTimeMillis() : 0;
try {
Object result = joinPoint.proceed();
if (performanceEnabled) {
long endTime = System.currentTimeMillis();
logger.info("方法执行时间:{}ms", endTime - startTime);
}
return result;
} catch (Exception e) {
if (loggingEnabled) {
logger.error("方法执行异常", e);
}
throw e;
}
}
}
6. 切面顺序管理
java
@Aspect
@Component
@Order(Ordered.HIGHEST_PRECEDENCE) // 最高优先级
public class SecurityAspect {
@Before("execution(* com.example.service.*.*(..))")
public void checkSecurity() {
// 安全检查应该最先执行
}
}
@Aspect
@Component
@Order(1)
public class LoggingAspect {
@Around("execution(* com.example.service.*.*(..))")
public Object logExecution(ProceedingJoinPoint joinPoint) throws Throwable {
// 日志记录
return joinPoint.proceed();
}
}
@Aspect
@Component
@Order(2)
public class PerformanceAspect {
@Around("execution(* com.example.service.*.*(..))")
public Object measurePerformance(ProceedingJoinPoint joinPoint) throws Throwable {
// 性能监控
return joinPoint.proceed();
}
}
@Aspect
@Component
@Order(Ordered.LOWEST_PRECEDENCE) // 最低优先级
public class CleanupAspect {
@After("execution(* com.example.service.*.*(..))")
public void cleanup() {
// 清理工作应该最后执行
}
}
调试和故障排除
1. 启用 AOP 调试
properties
# application.properties
logging.level.org.springframework.aop=DEBUG
logging.level.org.aspectj=DEBUG
# 显示代理创建信息
spring.aop.proxy-target-class=true
spring.aop.auto=true
2. 检查代理创建
java
@Component
public class AopDebugger implements ApplicationListener<ContextRefreshedEvent> {
@Autowired
private ApplicationContext applicationContext;
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
// 检查哪些 Bean 被代理了
String[] beanNames = applicationContext.getBeanDefinitionNames();
for (String beanName : beanNames) {
Object bean = applicationContext.getBean(beanName);
if (AopUtils.isAopProxy(bean)) {
logger.info("Bean {} 被 AOP 代理", beanName);
if (AopUtils.isJdkDynamicProxy(bean)) {
logger.info(" - 使用 JDK 动态代理");
} else if (AopUtils.isCglibProxy(bean)) {
logger.info(" - 使用 CGLIB 代理");
}
// 显示切面信息
if (bean instanceof Advised) {
Advised advised = (Advised) bean;
Advisor[] advisors = advised.getAdvisors();
for (Advisor advisor : advisors) {
logger.info(" - 应用切面:{}", advisor.getClass().getSimpleName());
}
}
}
}
}
}
3. 常见问题解决
java
// 问题1:内部方法调用不触发 AOP
@Service
public class UserService {
@Transactional
public void publicMethod() {
// 这个调用不会触发事务
this.privateMethod();
}
@Transactional
private void privateMethod() {
// 事务不会生效
}
}
// 解决方案:使用代理调用
@Service
public class UserService {
@Autowired
private UserService self;
@Transactional
public void publicMethod() {
// 通过代理调用,事务会生效
self.privateMethod();
}
@Transactional
public void privateMethod() {
// 现在事务会生效
}
}
// 问题2:切点表达式不匹配
@Aspect
@Component
public class DebuggingAspect {
@Before("execution(* com.example.service.*.*(..))")
public void debugPointcut(JoinPoint joinPoint) {
logger.debug("切点匹配:{}", joinPoint.getSignature());
}
// 添加更宽泛的切点进行调试
@Before("execution(* com.example..*.*(..))")
public void debugWidePointcut(JoinPoint joinPoint) {
logger.debug("宽泛切点匹配:{}", joinPoint.getSignature());
}
}
总结
Spring AOP 是一个强大的面向切面编程框架,它通过代理模式实现了横切关注点的模块化。主要特点包括:
核心优势
- 关注点分离:将横切关注点从业务逻辑中分离出来
- 代码复用:避免在多个地方重复相同的横切逻辑
- 松耦合:业务代码与横切关注点解耦
- 声明式编程:通过注解或配置声明切面行为
- 运行时织入:在运行时动态应用切面逻辑
适用场景
- 日志记录:统一的方法调用日志
- 性能监控:方法执行时间统计
- 安全控制:权限检查和身份验证
- 事务管理:声明式事务处理
- 缓存管理:自动缓存方法结果
- 异常处理:统一的异常处理逻辑
最佳实践总结
- 精确的切点表达式:避免过于宽泛的匹配
- 单一职责原则:每个切面只处理一种关注点
- 异常安全:确保切面逻辑不影响主业务流程
- 性能考虑:避免在高频调用的方法上使用复杂切面
- 测试友好:设计易于测试的切面
- 配置管理:支持动态开启/关闭切面功能
注意事项
- 代理限制:了解 JDK 动态代理和 CGLIB 代理的限制
- 内部调用:注意内部方法调用不会触发 AOP
- 性能影响:合理使用,避免过度使用影响性能
- 调试困难:AOP 可能会增加调试的复杂性
- 学习成本:需要理解 AOP 的概念和 AspectJ 表达式
Spring AOP 为企业级应用开发提供了强大的横切关注点处理能力,通过合理使用可以显著提高代码的可维护性和可重用性。在实际项目中,建议根据具体需求选择合适的切面策略,并遵循最佳实践来确保系统的稳定性和性能。