Spring AOP 原理详解
一、知识概述
AOP(Aspect-Oriented Programming,面向切面编程)是 Spring 框架的核心功能之一,它通过将横切关注点(Cross-cutting Concerns)从业务逻辑中分离出来,实现了关注点分离,提高了代码的模块化和可维护性。
AOP 主要应用场景:
- 日志记录:统一的方法调用日志
- 事务管理:声明式事务控制
- 权限校验:方法级别的权限控制
- 性能监控:方法执行时间统计
- 异常处理:统一的异常处理
理解 AOP 的原理,有助于更好地使用 Spring 的事务管理、缓存等高级特性。
二、知识点详细讲解
2.1 AOP 核心概念
切面(Aspect)
切面是横切关注点的模块化封装,包含通知和切点。
@Aspect
@Component
public class LoggingAspect {
// 切面 = 切点 + 通知
}
切点(Pointcut)
切点定义了通知在哪些连接点执行,使用切点表达式描述。
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceLayer() {}
通知(Advice)
通知定义了切面在特定连接点执行的动作:
- @Before:方法执行前
- @AfterReturning:方法成功返回后
- @AfterThrowing:方法抛出异常后
- @After:方法执行后(无论成功或异常)
- @Around:环绕方法执行(最强大)
连接点(Join Point)
连接点是程序执行的特定点,Spring AOP 中指方法执行。
目标对象(Target Object)
被通知的对象,即业务逻辑所在的类。
代理对象(Proxy)
AOP 框架创建的对象,包含通知逻辑。
织入(Weaving)
将切面应用到目标对象并创建代理对象的过程:
- 编译期织入:AspectJ 编译器
- 类加载期织入:AspectJ LTW
- 运行期织入:Spring AOP(动态代理)
2.2 切点表达式
execution 表达式
execution(修饰符模式? 返回类型模式 方法名模式(参数模式) 异常模式?)
示例:
execution(* com.example.service.*.*(..))
execution(public * *(..))
execution(* set*(..))
execution(* com.example.service.UserService+.*(..))
其他切点指示符
- within:匹配特定类型内的所有连接点
- this:匹配代理对象类型
- target:匹配目标对象类型
- args:匹配参数类型
- @annotation:匹配特定注解的方法
- @within:匹配特定注解的类
- @target:匹配特定注解的目标对象
- @args:匹配特定注解的参数
组合表达式
@Pointcut("execution(* com.example.service.*.*(..)) && args(id,..)")
public void serviceMethodWithId(Long id) {}
@Pointcut("execution(* com.example.service.*.*(..)) || execution(* com.example.dao.*.*(..))")
public void serviceOrDao() {}
@Pointcut("execution(* com.example.service.*.*(..)) && !@annotation(NoLog)")
public void serviceWithoutNoLog() {}
2.3 代理机制
JDK 动态代理
- 基于接口的代理
- 使用
java.lang.reflect.Proxy - 只能代理接口方法
Object proxy = Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
invocationHandler
);
CGLIB 代理
- 基于继承的代理
- 使用字节码生成技术
- 可以代理类(非 final)
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(methodInterceptor);
Object proxy = enhancer.create();
Spring 选择规则
- 目标对象实现了接口 → 默认使用 JDK 动态代理
- 目标对象没有实现接口 → 使用 CGLIB
- 强制使用 CGLIB:
@EnableAspectJAutoProxy(proxyTargetClass = true)
2.4 通知执行顺序
同一切面内的顺序
Around 前 → Before → 方法执行 → AfterReturning/AfterThrowing → After → Around 后
多切面的顺序
使用 @Order 或实现 Ordered 接口:
- Order 值越小,优先级越高
- Before 通知:Order 小的先执行
- After 通知:Order 大的先执行
2.5 AspectJ 注解
| 注解 | 说明 |
|---|---|
| @Aspect | 声明切面 |
| @Pointcut | 声明切点 |
| @Before | 前置通知 |
| @AfterReturning | 返回后通知 |
| @AfterThrowing | 异常通知 |
| @After | 后置通知 |
| @Around | 环绕通知 |
| @DeclareParents | 引入新功能 |
三、代码示例
3.1 基本切面示例
import org.aspectj.lang.*;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Autowired;
// 切面定义
@Aspect
@Component
public class LoggingAspect {
// 切点定义:匹配 Service 层所有方法
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceLayer() {}
// 切点定义:匹配特定注解
@Pointcut("@annotation(com.example.annotation.Loggable)")
public void loggableMethod() {}
// 前置通知
@Before("serviceLayer()")
public void logBefore(JoinPoint joinPoint) {
System.out.println("执行方法: " + joinPoint.getSignature().getName());
System.out.println("参数: " + java.util.Arrays.toString(joinPoint.getArgs()));
}
// 返回后通知
@AfterReturning(pointcut = "serviceLayer()", returning = "result")
public void logAfterReturning(JoinPoint joinPoint, Object result) {
System.out.println("方法返回: " + joinPoint.getSignature().getName());
System.out.println("返回值: " + result);
}
// 异常通知
@AfterThrowing(pointcut = "serviceLayer()", throwing = "error")
public void logAfterThrowing(JoinPoint joinPoint, Throwable error) {
System.out.println("方法异常: " + joinPoint.getSignature().getName());
System.out.println("异常: " + error.getMessage());
}
// 后置通知
@After("serviceLayer()")
public void logAfter(JoinPoint joinPoint) {
System.out.println("方法完成: " + joinPoint.getSignature().getName());
}
}
// 业务类
@Service
public class UserService {
public User findById(Long id) {
System.out.println("UserService.findById 执行");
return new User(id, "User-" + id);
}
public void save(User user) {
System.out.println("UserService.save 执行");
if (user.getId() == null) {
throw new IllegalArgumentException("用户ID不能为空");
}
}
}
// 辅助类
public class User {
private Long id;
private String name;
public User(Long id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "User{id=" + id + ", name='" + name + "'}";
}
}
3.2 环绕通知示例
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class AroundAspect {
// 环绕通知:最强大的通知类型
@Around("execution(* com.example.service.*.*(..))")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println("=== 方法开始: " + methodName + " ===");
System.out.println("参数: " + java.util.Arrays.toString(args));
try {
// 执行目标方法
Object result = joinPoint.proceed();
long endTime = System.currentTimeMillis();
System.out.println("执行时间: " + (endTime - startTime) + " ms");
System.out.println("返回值: " + result);
System.out.println("=== 方法结束: " + methodName + " ===\n");
return result;
} catch (Exception e) {
long endTime = System.currentTimeMillis();
System.out.println("执行时间: " + (endTime - startTime) + " ms");
System.out.println("异常: " + e.getClass().getSimpleName() + " - " + e.getMessage());
System.out.println("=== 方法异常结束: " + methodName + " ===\n");
// 可以选择重新抛出、返回默认值、或包装异常
throw e;
}
}
}
// 性能监控切面
@Aspect
@Component
public class PerformanceAspect {
@Around("execution(* com.example.service.*.*(..))")
public Object monitorPerformance(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.nanoTime();
try {
return joinPoint.proceed();
} finally {
long duration = System.nanoTime() - start;
double ms = duration / 1_000_000.0;
String className = joinPoint.getTarget().getClass().getSimpleName();
String methodName = joinPoint.getSignature().getName();
System.out.println(String.format("[%s.%s] 耗时: %.2f ms", className, methodName, ms));
}
}
}
3.3 注解驱动的切面
import java.lang.annotation.*;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.stereotype.Component;
// 自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Loggable {
String value() default "";
LogLevel level() default LogLevel.INFO;
}
public enum LogLevel {
DEBUG, INFO, WARN, ERROR
}
// 使用注解的业务方法
@Service
public class OrderService {
@Loggable(value = "创建订单", level = LogLevel.INFO)
public Order createOrder(Long userId, String productCode, int quantity) {
System.out.println("创建订单: userId=" + userId + ", product=" + productCode);
return new Order(System.currentTimeMillis(), userId, productCode, quantity);
}
@Loggable("取消订单")
public void cancelOrder(Long orderId) {
System.out.println("取消订单: " + orderId);
}
}
// 日志切面
@Aspect
@Component
public class LoggableAspect {
@Around("@annotation(loggable)")
public Object log(ProceedingJoinPoint joinPoint, Loggable loggable) throws Throwable {
String operation = loggable.value();
String methodName = joinPoint.getSignature().getName();
System.out.println("[" + loggable.level() + "] 开始 " + operation);
System.out.println(" 方法: " + methodName);
System.out.println(" 参数: " + java.util.Arrays.toString(joinPoint.getArgs()));
long startTime = System.currentTimeMillis();
try {
Object result = joinPoint.proceed();
long duration = System.currentTimeMillis() - startTime;
System.out.println("[" + loggable.level() + "] 完成 " + operation);
System.out.println(" 耗时: " + duration + " ms");
System.out.println(" 结果: " + result);
return result;
} catch (Throwable e) {
long duration = System.currentTimeMillis() - startTime;
System.out.println("[ERROR] 失败 " + operation);
System.out.println(" 耗时: " + duration + " ms");
System.out.println(" 异常: " + e.getMessage());
throw e;
}
}
}
// 订单类
public class Order {
private Long id;
private Long userId;
private String productCode;
private int quantity;
public Order(Long id, Long userId, String productCode, int quantity) {
this.id = id;
this.userId = userId;
this.productCode = productCode;
this.quantity = quantity;
}
@Override
public String toString() {
return "Order{id=" + id + ", userId=" + userId + ", productCode='" + productCode + "'}";
}
}
3.4 多切面协作示例
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
// 切面1:日志
@Aspect
@Component
@Order(1) // 优先级最高
public class LoggingAspect2 {
@Before("execution(* com.example.service.*.*(..))")
public void logBefore() {
System.out.println("[1-Logging] 开始记录日志");
}
@After("execution(* com.example.service.*.*(..))")
public void logAfter() {
System.out.println("[1-Logging] 结束记录日志");
}
}
// 切面2:事务
@Aspect
@Component
@Order(2)
public class TransactionAspect {
@Around("execution(* com.example.service.*.*(..))")
public Object manageTransaction(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("[2-Transaction] 开启事务");
try {
Object result = joinPoint.proceed();
System.out.println("[2-Transaction] 提交事务");
return result;
} catch (Exception e) {
System.out.println("[2-Transaction] 回滚事务");
throw e;
}
}
}
// 切面3:权限
@Aspect
@Component
@Order(3)
public class SecurityAspect {
@Before("execution(* com.example.service.*.*(..))")
public void checkPermission() {
System.out.println("[3-Security] 检查权限");
}
@AfterReturning("execution(* com.example.service.*.*(..))")
public void logSuccess() {
System.out.println("[3-Security] 权限验证通过");
}
}
// 执行顺序示例:
// [1-Logging] 开始记录日志
// [2-Transaction] 开启事务
// [3-Security] 检查权限
// 方法执行...
// [3-Security] 权限验证通过
// [2-Transaction] 提交事务
// [1-Logging] 结束记录日志
3.5 切点表达式详解
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class PointcutExpressionDemo {
// ==================== execution 表达式 ====================
// 匹配所有 public 方法
@Pointcut("execution(public * *(..))")
public void anyPublicMethod() {}
// 匹配 set 开头的方法
@Pointcut("execution(* set*(..))")
public void anySetMethod() {}
// 匹配特定包下的所有方法
@Pointcut("execution(* com.example.service.*.*(..))")
public void servicePackage() {}
// 匹配特定类的所有方法
@Pointcut("execution(* com.example.service.UserService.*(..))")
public void userServiceMethods() {}
// 匹配特定类及其子类的所有方法
@Pointcut("execution(* com.example.service.UserService+.*(..))")
public void userServiceAndSubclassMethods() {}
// 匹配特定参数类型的方法
@Pointcut("execution(* *(String, ..))")
public void firstParamString() {}
// 匹配无参方法
@Pointcut("execution(* *())")
public void noParamMethod() {}
// 匹配抛出特定异常的方法
@Pointcut("execution(* *.*(..) throws IOException)")
public void throwsIOException() {}
// ==================== within 表达式 ====================
// 匹配特定包下的所有方法
@Pointcut("within(com.example.service..*)")
public void withinServicePackage() {}
// 匹配特定类的所有方法
@Pointcut("within(com.example.service.UserService)")
public void withinUserService() {}
// ==================== @annotation 表达式 ====================
// 匹配带有特定注解的方法
@Pointcut("@annotation(org.springframework.transaction.annotation.Transactional)")
public void transactionalMethod() {}
// 匹配带有自定义注解的方法
@Pointcut("@annotation(com.example.annotation.Cacheable)")
public void cacheableMethod() {}
// ==================== @within 表达式 ====================
// 匹配带有特定注解的类的所有方法
@Pointcut("@within(org.springframework.stereotype.Service)")
public void withinServiceAnnotatedClass() {}
// ==================== args 表达式 ====================
// 匹配第一个参数为 Long 的方法
@Pointcut("args(Long, ..)")
public void firstArgLong() {}
// 匹配参数类型组合
@Pointcut("args(Long, String)")
public void argsLongString() {}
// ==================== 组合表达式 ====================
// && 与运算
@Pointcut("execution(* com.example.service.*.*(..)) && args(id,..)")
public void serviceMethodWithFirstArg(Object id) {}
// || 或运算
@Pointcut("execution(* com.example.service.*.*(..)) || execution(* com.example.dao.*.*(..))")
public void serviceOrDao() {}
// ! 非运算
@Pointcut("execution(* com.example.service.*.*(..)) && !execution(* com.example.service.*.get*(..))")
public void serviceNonGetter() {}
// ==================== 引用切点 ====================
@Before("servicePackage() && args(id)")
public void beforeServiceMethod(Object id) {
System.out.println("Service 方法执行,第一个参数: " + id);
}
@AfterReturning(pointcut = "anyPublicMethod()", returning = "result")
public void afterPublicMethod(Object result) {
System.out.println("Public 方法返回: " + result);
}
}
3.6 引入(Introduction)示例
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.stereotype.Component;
// 新增的接口
public interface Auditable {
void setLastModifiedBy(String user);
String getLastModifiedBy();
}
// 接口实现
public class AuditableImpl implements Auditable {
private String lastModifiedBy;
@Override
public void setLastModifiedBy(String user) {
this.lastModifiedBy = user;
}
@Override
public String getLastModifiedBy() {
return lastModifiedBy;
}
}
// 切面:为现有类引入新接口
@Aspect
@Component
public class IntroductionAspect {
// 为所有 Service 类引入 Auditable 接口
@DeclareParents(
value = "com.example.service.*+",
defaultImpl = AuditableImpl.class
)
public static Auditable auditable;
}
// 使用示例
@Service
public class ProductService {
// 原有类不需要修改
}
public class IntroductionDemo {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
ProductService productService = context.getBean(ProductService.class);
// Product 原本没有实现 Auditable,但通过 @DeclareParents 引入了
Auditable auditable = (Auditable) productService;
auditable.setLastModifiedBy("admin");
System.out.println("最后修改人: " + auditable.getLastModifiedBy());
}
}
四、实战应用场景
4.1 统一异常处理
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.stereotype.Component;
import java.util.*;
@Aspect
@Component
public class ExceptionHandlingAspect {
// 异常处理器映射
private final Map<Class<? extends Throwable>, ExceptionHandler> handlers = new HashMap<>();
public ExceptionHandlingAspect() {
handlers.put(IllegalArgumentException.class, this::handleIllegalArgument);
handlers.put(NullPointerException.class, this::handleNullPointer);
}
@Around("execution(* com.example.service.*.*(..))")
public Object handleException(ProceedingJoinPoint joinPoint) throws Throwable {
try {
return joinPoint.proceed();
} catch (Throwable e) {
return handleExceptionInternal(e, joinPoint);
}
}
private Object handleExceptionInternal(Throwable e, ProceedingJoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println("异常发生在方法: " + methodName);
System.out.println("参数: " + Arrays.toString(args));
System.out.println("异常类型: " + e.getClass().getSimpleName());
System.out.println("异常信息: " + e.getMessage());
// 查找对应的异常处理器
ExceptionHandler handler = handlers.get(e.getClass());
if (handler != null) {
return handler.handle(e, joinPoint);
}
// 默认处理
return createErrorResponse("系统异常", 500);
}
private Object handleIllegalArgument(Throwable e, ProceedingJoinPoint joinPoint) {
return createErrorResponse("参数错误: " + e.getMessage(), 400);
}
private Object handleNullPointer(Throwable e, ProceedingJoinPoint joinPoint) {
return createErrorResponse("空指针异常", 500);
}
private Response createErrorResponse(String message, int code) {
return new Response(code, message, null);
}
@FunctionalInterface
interface ExceptionHandler {
Object handle(Throwable e, ProceedingJoinPoint joinPoint);
}
}
// 响应类
public class Response {
private int code;
private String message;
private Object data;
public Response(int code, String message, Object data) {
this.code = code;
this.message = message;
this.data = data;
}
@Override
public String toString() {
return "Response{code=" + code + ", message='" + message + "'}";
}
}
4.2 方法缓存
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.lang.annotation.*;
// 缓存注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CacheResult {
String key() default "";
int expire() default 300; // 秒
}
// 简单缓存实现
@Component
public class SimpleCache {
private final Map<String, CacheEntry> cache = new ConcurrentHashMap<>();
public Object get(String key) {
CacheEntry entry = cache.get(key);
if (entry != null && !entry.isExpired()) {
return entry.value;
}
return null;
}
public void put(String key, Object value, int expireSeconds) {
cache.put(key, new CacheEntry(value, System.currentTimeMillis() + expireSeconds * 1000));
}
public void clear() {
cache.clear();
}
private static class CacheEntry {
Object value;
long expireTime;
CacheEntry(Object value, long expireTime) {
this.value = value;
this.expireTime = expireTime;
}
boolean isExpired() {
return System.currentTimeMillis() > expireTime;
}
}
}
// 缓存切面
@Aspect
@Component
public class CacheAspect {
private final SimpleCache cache;
public CacheAspect(SimpleCache cache) {
this.cache = cache;
}
@Around("@annotation(cacheResult)")
public Object cache(ProceedingJoinPoint joinPoint, CacheResult cacheResult) throws Throwable {
// 生成缓存键
String key = generateKey(joinPoint, cacheResult.key());
// 尝试从缓存获取
Object cachedValue = cache.get(key);
if (cachedValue != null) {
System.out.println("缓存命中: " + key);
return cachedValue;
}
// 执行方法
System.out.println("缓存未命中,执行方法: " + key);
Object result = joinPoint.proceed();
// 存入缓存
if (result != null) {
cache.put(key, result, cacheResult.expire());
}
return result;
}
private String generateKey(ProceedingJoinPoint joinPoint, String keyPrefix) {
String className = joinPoint.getTarget().getClass().getSimpleName();
String methodName = joinPoint.getSignature().getName();
String args = Arrays.toString(joinPoint.getArgs());
return keyPrefix.isEmpty()
? className + "." + methodName + ":" + args
: keyPrefix + ":" + args;
}
}
// 使用示例
@Service
public class DataService {
@CacheResult(key = "user", expire = 60)
public User getUserById(Long id) {
System.out.println("查询数据库: SELECT * FROM user WHERE id = " + id);
return new User(id, "User-" + id);
}
@CacheResult
public List<User> getAllUsers() {
System.out.println("查询数据库: SELECT * FROM user");
return Arrays.asList(
new User(1L, "User-1"),
new User(2L, "User-2")
);
}
}
4.3 限流切面
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.stereotype.Component;
import java.lang.annotation.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;
// 限流注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimit {
int value() default 10; // 每秒请求数
int timeout() default 1000; // 等待超时时间(ms)
}
// 限流器
@Component
public class RateLimiter {
private final Map<String, Semaphore> limiters = new ConcurrentHashMap<>();
public boolean tryAcquire(String key, int permits, int timeout) {
Semaphore semaphore = limiters.computeIfAbsent(key, k -> new Semaphore(permits));
try {
return semaphore.tryAcquire(timeout, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
public void release(String key) {
Semaphore semaphore = limiters.get(key);
if (semaphore != null) {
semaphore.release();
}
}
}
// 限流切面
@Aspect
@Component
public class RateLimitAspect {
private final RateLimiter rateLimiter;
private final AtomicLong requestCount = new AtomicLong(0);
public RateLimitAspect(RateLimiter rateLimiter) {
this.rateLimiter = rateLimiter;
}
@Around("@annotation(rateLimit)")
public Object limit(ProceedingJoinPoint joinPoint, RateLimit rateLimit) throws Throwable {
String key = joinPoint.getSignature().toLongString();
long count = requestCount.incrementAndGet();
System.out.println("请求计数: " + count);
if (!rateLimiter.tryAcquire(key, 1, rateLimit.timeout())) {
System.out.println("限流触发: " + key);
throw new RuntimeException("请求过于频繁,请稍后再试");
}
try {
return joinPoint.proceed();
} finally {
rateLimiter.release(key);
}
}
}
// 使用示例
@Service
public class ApiClient {
@RateLimit(value = 5, timeout = 500) // 每秒最多5次
public String callExternalApi(String endpoint) {
System.out.println("调用外部API: " + endpoint);
return "Response from " + endpoint;
}
}
4.4 审计日志
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.stereotype.Component;
import java.lang.annotation.*;
import java.time.LocalDateTime;
// 审计注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Audit {
String action();
String description() default "";
}
// 审计日志
public class AuditLog {
private String action;
private String description;
private String className;
private String methodName;
private Object[] args;
private Object result;
private Throwable exception;
private LocalDateTime timestamp;
private long duration;
// getter/setter 和构造器
// ...
@Override
public String toString() {
return String.format("[%s] %s - %s.%s() 耗时:%dms %s",
timestamp, action, className, methodName, duration,
exception != null ? "异常:" + exception.getMessage() : "成功");
}
}
// 审计切面
@Aspect
@Component
public class AuditAspect {
private final List<AuditLog> auditLogs = new CopyOnWriteArrayList<>();
@Around("@annotation(audit)")
public Object audit(ProceedingJoinPoint joinPoint, Audit audit) throws Throwable {
AuditLog log = new AuditLog();
log.setAction(audit.action());
log.setDescription(audit.description());
log.setClassName(joinPoint.getTarget().getClass().getSimpleName());
log.setMethodName(joinPoint.getSignature().getName());
log.setArgs(joinPoint.getArgs());
log.setTimestamp(LocalDateTime.now());
long startTime = System.currentTimeMillis();
try {
Object result = joinPoint.proceed();
log.setResult(result);
return result;
} catch (Throwable e) {
log.setException(e);
throw e;
} finally {
log.setDuration(System.currentTimeMillis() - startTime);
auditLogs.add(log);
System.out.println(log);
}
}
public List<AuditLog> getAuditLogs() {
return new ArrayList<>(auditLogs);
}
}
// 使用示例
@Service
public class AccountService {
@Audit(action = "转账", description = "用户转账操作")
public void transfer(Long fromId, Long toId, BigDecimal amount) {
System.out.println(String.format("转账: %d -> %d, 金额: %s", fromId, toId, amount));
}
@Audit(action = "开户", description = "创建新账户")
public Long createAccount(String username) {
System.out.println("创建账户: " + username);
return System.currentTimeMillis();
}
}
五、代理机制深入
5.1 JDK 动态代理示例
import java.lang.reflect.*;
// 接口
public interface UserService {
User findById(Long id);
void save(User user);
}
// 实现类
public class UserServiceImpl implements UserService {
@Override
public User findById(Long id) {
System.out.println("UserServiceImpl.findById: " + id);
return new User(id, "User-" + id);
}
@Override
public void save(User user) {
System.out.println("UserServiceImpl.save: " + user);
}
}
// JDK 动态代理
public class JdkProxyDemo {
public static void main(String[] args) {
// 保存生成的代理类
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
UserService target = new UserServiceImpl();
UserService proxy = (UserService) Proxy.newProxyInstance(
JdkProxyDemo.class.getClassLoader(),
new Class<?>[] { UserService.class },
new LogInvocationHandler(target)
);
proxy.findById(1L);
proxy.save(new User(2L, "Test"));
}
}
// 调用处理器
class LogInvocationHandler implements InvocationHandler {
private final Object target;
public LogInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("=== JDK 代理前 ===");
System.out.println("方法: " + method.getName());
System.out.println("参数: " + Arrays.toString(args));
long startTime = System.currentTimeMillis();
try {
Object result = method.invoke(target, args);
System.out.println("返回: " + result);
return result;
} finally {
System.out.println("耗时: " + (System.currentTimeMillis() - startTime) + " ms");
System.out.println("=== JDK 代理后 ===\n");
}
}
}
5.2 CGLIB 代理示例
import org.springframework.cglib.proxy.*;
// 目标类(无接口)
public class OrderService {
public void createOrder(String productId, int quantity) {
System.out.println("创建订单: productId=" + productId + ", quantity=" + quantity);
}
public Order getOrder(Long id) {
System.out.println("获取订单: " + id);
return new Order(id, "Product-" + id);
}
}
// CGLIB 代理
public class CglibProxyDemo {
public static void main(String[] args) {
// 保存生成的代理类
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "./cglib");
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OrderService.class);
enhancer.setCallback(new LogMethodInterceptor());
OrderService proxy = (OrderService) enhancer.create();
proxy.createOrder("P001", 2);
proxy.getOrder(1L);
}
}
// 方法拦截器
class LogMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("=== CGLIB 代理前 ===");
System.out.println("方法: " + method.getName());
System.out.println("参数: " + Arrays.toString(args));
long startTime = System.currentTimeMillis();
try {
// 调用父类方法(避免递归)
Object result = proxy.invokeSuper(obj, args);
System.out.println("返回: " + result);
return result;
} finally {
System.out.println("耗时: " + (System.currentTimeMillis() - startTime) + " ms");
System.out.println("=== CGLIB 代理后 ===\n");
}
}
}
5.3 代理选择策略
import org.springframework.context.annotation.*;
import org.springframework.aop.framework.*;
@Configuration
@EnableAspectJAutoProxy
public class ProxyConfig {
// 强制使用 CGLIB 代理
@EnableAspectJAutoProxy(proxyTargetClass = true)
public static class ForceCglibConfig {}
// 使用 JDK 代理(默认)
@EnableAspectJAutoProxy(proxyTargetClass = false)
public static class JdkProxyConfig {}
}
// 代理检测工具
public class ProxyUtils {
public static void printProxyInfo(Object bean) {
Class<?> clazz = bean.getClass();
if (AopUtils.isAopProxy(bean)) {
System.out.println("是 AOP 代理");
if (AopUtils.isCglibProxy(bean)) {
System.out.println("代理类型: CGLIB");
System.out.println("父类: " + clazz.getSuperclass().getSimpleName());
} else if (AopUtils.isJdkDynamicProxy(bean)) {
System.out.println("代理类型: JDK 动态代理");
System.out.println("接口: " + Arrays.toString(clazz.getInterfaces()));
}
// 获取目标类
try {
Object target = AopProxyUtils.getSingletonTarget(bean);
System.out.println("目标类: " + target.getClass().getSimpleName());
} catch (Exception e) {
System.out.println("无法获取目标类");
}
} else {
System.out.println("不是代理");
}
}
}
六、总结与最佳实践
最佳实践
-
切面设计原则
- 单一职责:每个切面只关注一个横切关注点
- 命名清晰:切面和方法名要有意义
- 避免过度使用:不是所有需求都适合 AOP
-
切点表达式优化
- 使用命名切点,提高可读性
- 避免过于宽泛的切点表达式
- 合理使用组合表达式
-
通知选择
- 优先使用最简单的通知类型
- Around 最强大但也最复杂
- 注意通知的执行顺序
-
代理机制选择
- 有接口:JDK 动态代理
- 无接口:CGLIB
- Spring Boot 默认使用 CGLIB
常见陷阱
-
内部方法调用
- 问题:同类内部方法调用不会触发代理
- 解决:注入自身或使用 AopContext
-
final 方法
- 问题:CGLIB 无法代理 final 方法
- 解决:移除 final 修饰符
-
private 方法
- 问题:Spring AOP 只代理 public 方法
- 解决:改为 public 或使用 AspectJ
-
通知顺序混乱
- 问题:多切面时执行顺序不确定
- 解决:使用 @Order 注解
Spring AOP 是实现横切关注点分离的强大工具。理解其原理和最佳实践,能够帮助我们更好地实现日志、事务、权限等功能,提高代码的可维护性和可扩展性。