一、AOP 基础概念与核心组件

核心思想

AOP(面向切面编程)是一种通过预编译或运行时动态代理实现横切关注点(Cross-Cutting Concerns)模块化的编程范式。其核心目标是:

解耦:将与业务无关的代码(如日志、权限)从核心逻辑中剥离
可维护性:集中管理横切逻辑,避免散落在各业务模块
灵活性:通过配置快速启用/禁用功能模块,无需修改源码

核心术语

术语 描述 示例
切面(Aspect) 横切关注点的模块化实现,包含通知和切入点定义 日志切面、权限切面
连接点(Join Point) 程序执行过程中的特定节点(如方法调用、异常抛出) UserService.getUser()方法执行
切入点(Pointcut) 通过表达式定义需要拦截的连接点集合 execution(* com.example.service.*.*(..))
通知(Advice) 在切入点执行的增强逻辑,分为五类:
@Before/@After/@Around/@AfterReturning/@AfterThrowing
记录方法耗时、异常报警
织入(Weaving) 将切面逻辑插入目标对象的过程,Spring AOP 采用动态代理实现 JDK动态代理(接口)或CGLIB代理(类)

关键特性
Spring AOP 属于运行时增强,仅支持方法级别的连接点(不支持字段/构造器)


二、Spring Boot 集成实践

基础配置步骤

添加依赖

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

该依赖已包含 AspectJ 注解支持,无需额外配置

启用自动代理
通过 @EnableAspectJAutoProxy 开启(Spring Boot 默认自动配置)

定义切面类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Aspect
@Component
@Order(1) // 控制切面执行顺序
public class LoggingAspect {
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceLayer() {}

@Around("serviceLayer()")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
Object result = joinPoint.proceed();
log.info("Method {} executed in {} ms",
joinPoint.getSignature(),
System.currentTimeMillis() start);
return result;
}
}

代码说明
@Aspect 标识切面类,@Component 使其被 Spring 管理
@Order 控制多个切面的执行顺序(值越小优先级越高)

切入点表达式类型

表达式类型 示例 适用场景
execution execution(* com.service.*.*(..)) 匹配方法执行
within within(com.controller..*) 匹配包/类内所有方法
@annotation @annotation(com.example.Log) 匹配带有特定注解的方法
args args(java.lang.String, ..) 匹配参数类型

三、典型应用场景深度解析

统一日志管理

实现方式
使用 @Around 记录方法耗时
通过 JoinPoint 获取方法签名、参数
结合 SLF4J 输出结构化日志

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Aspect
@Component
public class WebLogAspect {
@Pointcut("within(@org.springframework.web.bind.annotation.RestController *)")
public void webLog() {}

@Around("webLog()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
ServletRequestAttributes attributes =
(ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
HttpServletRequest request = attributes.getRequest();

// 记录请求信息
log.info("URL: {}, Method: {}, IP: {}",
request.getRequestURL(),
request.getMethod(),
request.getRemoteAddr());

long start = System.currentTimeMillis();
Object result = joinPoint.proceed();
log.info("Response Time: {} ms", System.currentTimeMillis() start);
return result;
}
}

技术要点
使用 RequestContextHolder 在非 Controller 层获取请求对象

权限控制

实现方式
自定义 @PreAuthorize 注解
结合 Spring Security 的权限表达式

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Aspect
@Component
public class SecurityAspect {
@Before("@annotation(com.example.PreAuthorize) && args(id, ..)")
public void checkPermission(JoinPoint joinPoint) {
Object[] args = joinPoint.getArgs();
String userId = (String) args[0];

Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (!authentication.getName().equals(userId)) {
throw new AccessDeniedException("Permission denied");
}
}
}

事务管理增强

实现方式
通过 @Transactional 注解的环绕通知
实现多数据源动态切换(读写分离)

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Aspect
@Component
@Order(0)
public class DataSourceAspect {
@Pointcut("@annotation(com.example.ReadOnly)")
public void readOnlyPointcut() {}

@Before("readOnlyPointcut()")
public void setReadDataSource() {
DatabaseContextHolder.set(DatabaseType.SLAVE);
}

@After("readOnlyPointcut()")
public void restoreDataSource() {
DatabaseContextHolder.clear();
}
}

四、高级技巧与最佳实践

自定义注解增强

实现步骤
定义注解:

1
2
3
4
5
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TimeMonitor {
String metricName() default "";
}

切面处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Aspect
@Component
public class TimeMonitorAspect {
@Around("@annotation(monitor)")
public Object monitorTime(ProceedingJoinPoint joinPoint, TimeMonitor monitor) throws Throwable {
long start = System.nanoTime();
Object result = joinPoint.proceed();
long duration = System.nanoTime() start;

Metrics.record(monitor.metricName(), duration);
return result;
}
}

优势:通过注解声明式配置监控指标,避免硬编码

多切面执行顺序控制

控制方式 实现方法
@Order 注解 类级别注解,数值越小优先级越高(如事务切面通常设为最高)
实现 Ordered 接口 重写 getOrder() 方法返回优先级数值

异常处理增强

实现方式

1
2
3
4
5
6
7
8
9
10
@Aspect
@Component
public class ExceptionAspect {
@AfterThrowing(pointcut = "execution(* com.example..*(..))", throwing = "ex")
public void handleException(JoinPoint joinPoint, Exception ex) {
String methodName = joinPoint.getSignature().toShortString();
ErrorTracker.report(methodName, ex);
AlertService.sendCriticalAlert("Exception in " + methodName);
}
}

五、常见问题与解决方案

切面不生效排查

可能原因 解决方案
Bean 未被 Spring 管理 检查切面类是否添加 @Component 或其他 Stereotype 注解
切入点表达式错误 使用 AopUtils 工具类调试匹配结果
代理模式限制 CGLIB 代理需确保类和方法非 final

性能优化建议

避免在切面中执行耗时操作(如远程调用)
使用条件化切入点减少匹配范围(如 @within 替代宽泛的 execution
对高频调用方法禁用非必要切面


六、扩展应用场景

分布式缓存管理

实现方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Aspect
@Component
public class CacheAspect {
@Autowired
private CacheManager cacheManager;

@Around("@annotation(cacheable)")
public Object handleCache(ProceedingJoinPoint joinPoint, Cacheable cacheable) {
String key = generateCacheKey(joinPoint);
Cache cache = cacheManager.getCache(cacheable.value());

return cache.get(key, () -> {
try {
return joinPoint.proceed();
} catch (Throwable e) {
throw new RuntimeException(e);
}
});
}
}

可结合 Redis、Ehcache 等实现

在这里插入图片描述

API 版本控制

实现方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Aspect
@Component
public class VersionAspect {
@Around("@annotation(apiVersion) && within(com.example.controller..*)")
public Object checkVersion(ProceedingJoinPoint joinPoint, ApiVersion apiVersion) {
RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
HttpServletRequest request = ((ServletRequestAttributes) attributes).getRequest();

String clientVersion = request.getHeader("X-API-Version");
if (!apiVersion.value().equals(clientVersion)) {
throw new VersionMismatchException("Unsupported API version");
}
return joinPoint.proceed();
}
}