AOP 应用场景:事务、日志、性能监控、权限校验
学会了 AOP 的概念和语法,终于到了实战环节。
AOP 在实际开发中有无数应用场景。这一节,我们来看看最常见的四个:事务管理、统一日志、性能监控、权限校验。
场景一:事务管理
这是 AOP 最经典的应用。
原始写法
java
@Service
public class OrderService {
@Autowired
private TransactionTemplate transactionTemplate;
public void createOrder(Order order) {
transactionTemplate.executeWithoutResult(status -> {
try {
// 1. 创建订单
orderMapper.insert(order);
// 2. 扣减库存
inventoryService.reduce(order.getProductId(), order.getQuantity());
// 3. 发送通知
notificationService.notify(order);
} catch (Exception e) {
status.setRollbackOnly();
throw e;
}
});
}
}AOP 写法
java
// 定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Transactional {
}
// 定义切面
@Aspect
@Component
public class TransactionAspect {
@Autowired
private TransactionManager transactionManager;
@Around("@annotation(transactional)")
public Object around(ProceedingJoinPoint pjp, Transactional transactional) throws Throwable {
TransactionStatus status = transactionManager.getTransaction(
new DefaultTransactionDefinition());
try {
Object result = pjp.proceed();
transactionManager.commit(status);
return result;
} catch (Exception e) {
transactionManager.rollback(status);
throw e;
}
}
}
// 使用
@Service
public class OrderService {
@Transactional // 简洁!
public void createOrder(Order order) {
orderMapper.insert(order);
inventoryService.reduce(order.getProductId(), order.getQuantity());
notificationService.notify(order);
}
}对比:代码从 12 行减少到 4 行,核心逻辑一目了然。
场景二:统一日志记录
原始写法
java
@Service
public class UserService {
public void createUser(User user) {
log.info("创建用户开始: {}", user.getName());
try {
// 业务逻辑
userMapper.insert(user);
log.info("创建用户成功: {}", user.getName());
} catch (Exception e) {
log.error("创建用户失败: {}", user.getName(), e);
throw e;
}
}
public void deleteUser(Long id) {
log.info("删除用户开始: id={}", id);
try {
// 业务逻辑
userMapper.deleteById(id);
log.info("删除用户成功: id={}", id);
} catch (Exception e) {
log.error("删除用户失败: id={}", id, e);
throw e;
}
}
}AOP 写法
java
// 定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OperLog {
String value(); // 操作描述
}
// 定义切面
@Aspect
@Component
@Slf4j
public class LoggingAspect {
@Around("@annotation(operLog)")
public Object around(ProceedingJoinPoint pjp, OperLog operLog) throws Throwable {
String operation = operLog.value();
MethodSignature signature = (MethodSignature) pjp.getSignature();
String className = signature.getDeclaringType().getSimpleName();
String methodName = signature.getName();
// 前置日志
log.info("[{}] {}.{} 开始, 参数: {}",
operation, className, methodName, Arrays.toString(pjp.getArgs()));
long start = System.currentTimeMillis();
try {
Object result = pjp.proceed();
// 成功日志
log.info("[{}] {}.{} 成功, 耗时: {}ms",
operation, className, methodName, System.currentTimeMillis() - start);
return result;
} catch (Exception e) {
// 失败日志
log.error("[{}] {}.{} 失败: {}",
operation, className, methodName, e.getMessage(), e);
throw e;
}
}
}
// 使用
@Service
public class UserService {
@OperLog("创建用户")
public void createUser(User user) {
userMapper.insert(user);
}
@OperLog("删除用户")
public void deleteUser(Long id) {
userMapper.deleteById(id);
}
@OperLog("更新用户")
public void updateUser(User user) {
userMapper.updateById(user);
}
}好处:
- 核心业务逻辑不再被日志代码污染
- 所有方法的日志格式统一
- 可以集中管理和修改
场景三:性能监控
需求
- 记录每个方法的执行时间
- 超过阈值的记录警告日志
- 关键方法慢查询追踪
实现
java
// 定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Performance {
long warnThreshold() default 1000; // 警告阈值(毫秒)
}
// 定义切面
@Aspect
@Component
@Slf4j
public class PerformanceAspect {
@Around("@annotation(performance)")
public Object around(ProceedingJoinPoint pjp, Performance performance) throws Throwable {
MethodSignature signature = (MethodSignature) pjp.getSignature();
String className = signature.getDeclaringType().getSimpleName();
String methodName = signature.getName();
long start = System.currentTimeMillis();
try {
return pjp.proceed();
} finally {
long duration = System.currentTimeMillis() - start;
long warnThreshold = performance.warnThreshold();
if (duration > warnThreshold) {
log.warn("⚠️ [性能警告] {}.{} 执行耗时: {}ms (阈值: {}ms)",
className, methodName, duration, warnThreshold);
} else {
log.debug("{}.{} 执行耗时: {}ms", className, methodName, duration);
}
// 超过 5 秒的详细记录
if (duration > 5000) {
log.error("🚨 [严重慢查询] {}.{} 执行耗时: {}ms, 参数: {}",
className, methodName, duration, Arrays.toString(pjp.getArgs()));
}
}
}
}
// 使用
@Service
public class OrderService {
@Performance(warnThreshold = 500) // 超过 500ms 警告
public Page<Order> searchOrders(OrderQuery query) {
return orderMapper.search(query);
}
@Performance(warnThreshold = 1000) // 超过 1s 警告
public OrderDetail getOrderDetail(Long orderId) {
// 查询订单详情
}
@Performance // 使用默认阈值 1000ms
public void exportOrders(ExportQuery query) {
// 导出订单
}
}日志输出示例
2024-01-15 10:00:01 [PerformanceAspect] INFO - orderService.searchOrders 执行耗时: 320ms
2024-01-15 10:00:05 [PerformanceAspect] WARN - ⚠️ [性能警告] orderService.getOrderDetail 执行耗时: 1500ms (阈值: 1000ms)
2024-01-15 10:01:00 [PerformanceAspect] ERROR - 🚨 [严重慢查询] orderService.exportOrders 执行耗时: 8500ms, 参数: [...]场景四:权限校验
需求
- 接口需要登录才能访问
- 某些接口需要特定角色
- 某些接口需要特定权限
实现
java
// 定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresPermission {
String value(); // 权限码
}
// 定义切面
@Aspect
@Component
@Slf4j
public class SecurityAspect {
@Autowired
private UserContext userContext; // 当前用户上下文
@Around("@annotation(requiresPermission)")
public Object around(ProceedingJoinPoint pjp, RequiresPermission requiresPermission) throws Throwable {
String requiredPermission = requiresPermission.value();
// 1. 检查是否登录
if (!userContext.isAuthenticated()) {
throw new UnauthorizedException("请先登录");
}
// 2. 检查是否有权限
if (!userContext.hasPermission(requiredPermission)) {
log.warn("权限不足: 需要权限 {}, 当前用户: {}",
requiredPermission, userContext.getCurrentUser());
throw new ForbiddenException("权限不足");
}
// 3. 继续执行
return pjp.proceed();
}
}
// 用户上下文
@Component
public class UserContext {
private static final ThreadLocal<User> currentUser = new ThreadLocal<>();
public boolean isAuthenticated() {
return currentUser.get() != null;
}
public boolean hasPermission(String permission) {
User user = currentUser.get();
if (user == null) {
return false;
}
// 检查用户权限
return user.getPermissions().contains(permission);
}
public User getCurrentUser() {
return currentUser.get();
}
}
// 异常定义
public class UnauthorizedException extends RuntimeException {
public UnauthorizedException(String message) {
super(message);
}
}
public class ForbiddenException extends RuntimeException {
public ForbiddenException(String message) {
super(message);
}
}
// 使用
@RestController
@RequestMapping("/api/admin")
public class AdminController {
@RequiresPermission("user:create")
@PostMapping("/users")
public Result<User> createUser(@RequestBody UserDTO dto) {
// 只有拥有 user:create 权限才能访问
}
@RequiresPermission("user:delete")
@DeleteMapping("/users/{id}")
public Result<Void> deleteUser(@PathVariable Long id) {
// 只有拥有 user:delete 权限才能访问
}
@RequiresPermission("role:assign")
@PostMapping("/roles/assign")
public Result<Void> assignRole(@RequestBody RoleAssignDTO dto) {
// 只有拥有 role:assign 权限才能访问
}
}更多应用场景
1. 统一异常处理
java
@Aspect
@Component
public class ExceptionHandleAspect {
@AfterThrowing(
pointcut = "execution(* com.example..*(..))",
throwing = "e"
)
public void handleException(JoinPoint pjp, Exception e) {
// 统一记录异常日志
// 发送告警通知
// 返回统一的错误响应
}
}2. 数据校验
java
@Aspect
@Component
public class ValidationAspect {
@Before("execution(* com.example.service..*.save*(..)) || execution(* com.example.service..*.update*(..))")
public void validate(JoinPoint pjp) {
Object[] args = pjp.getArgs();
for (Object arg : args) {
if (arg instanceof Validatable) {
((Validatable) arg).validate();
}
}
}
}3. 审计日志
java
@Aspect
@Component
public class AuditAspect {
@Around("@annotation(auditable)")
public Object around(ProceedingJoinPoint pjp, Auditable auditable) throws Throwable {
String action = auditable.value();
User currentUser = getCurrentUser();
// 记录操作前的状态
Object beforeState = getState(pjp);
Object result = pjp.proceed();
// 记录审计日志
AuditLog log = new AuditLog();
log.setUser(currentUser.getUsername());
log.setAction(action);
log.setTarget(getTarget(pjp));
log.setBeforeState(beforeState);
log.setAfterState(result);
log.setTimestamp(new Date());
auditLogMapper.insert(log);
return result;
}
}面试核心问题
Q1:AOP 在实际开发中有哪些应用场景?
- 事务管理:@Transactional
- 日志记录:统一日志、审计日志
- 性能监控:方法执行时间监控
- 权限校验:接口权限控制
- 异常处理:统一异常处理
- 缓存:@Cacheable
- 数据校验:参数校验
Q2:自定义注解 + AOP 的开发模式有什么好处?
- 声明式:用注解声明意图,代码更简洁
- 可读性:业务方法专注于业务逻辑
- 可维护性:增强逻辑集中管理
- 可复用:注解可以在多处复用
Q3:如何设计一个好的 AOP 注解?
- 语义清晰:注解名称要表达意图
- 参数简洁:必要的参数,不需要的不要加
- 默认值合理:大多数场景可以直接使用
- 文档完善:说明使用场景和示例
总结
┌────────────────────────────────────────────────────────────┐
│ AOP 应用场景总结 │
├────────────────────────────────────────────────────────────┤
│ │
│ 事务管理 │
│ → @Transactional → 简化事务控制 │
│ │
│ 统一日志 │
│ → @OperLog → 统一记录操作日志 │
│ │
│ 性能监控 │
│ → @Performance → 方法耗时监控 │
│ │
│ 权限校验 │
│ → @RequiresPermission → 接口权限控制 │
│ │
│ 设计模式:自定义注解 + AOP 切面 = 声明式增强 │
│ │
└────────────────────────────────────────────────────────────┘下节预告:Spring 事务管理:@Transactional 原理 —— 从源码层面理解 @Transactional 是如何工作的。
