📋 学习目标
- 理解AOP概念 - 面向切面编程思想
- 掌握核心术语 - 切面、切点、通知等
- 学会配置AOP - 注解与XML配置
- 实现通知类型 - 前置、后置、环绕通知
- 掌握切点表达式 - 精确定位目标方法
- 应用实战场景 - 日志、事务、权限控制
🔍 AOP核心概念
- 切面(Aspect) - 横切关注点的模块化
- 连接点(JoinPoint) - 程序执行的特定点
- 切点(Pointcut) - 连接点的集合
- 通知(Advice) - 切面在特定连接点执行的动作
- 目标对象(Target) - 被通知的对象
- 代理(Proxy) - AOP框架创建的对象
📦 AOP依赖配置
<!-- pom.xml AOP依赖配置 -->
<dependencies>
<!-- SpringBoot AOP启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- SpringBoot Web启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- SpringBoot 测试启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
# application.yml AOP配置
spring:
aop:
# 启用AOP自动配置
auto: true
# 使用CGLIB代理
proxy-target-class: true
# 日志配置
logging:
level:
cn.bugstack.springframework.boot.aop: DEBUG
org.springframework.aop: DEBUG
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
🔄 AOP执行流程
5
后置通知
执行@After、@AfterReturning等通知
🎯 切面实现示例
// LoggingAspect.java - 日志切面
@Aspect
@Component
@Slf4j
public class LoggingAspect {
/**
* 定义切点:拦截service包下所有类的所有方法
*/
@Pointcut("execution(* cn.bugstack.springframework.boot.aop.service.*.*(..))")
public void servicePointcut() {}
/**
* 前置通知:方法执行前记录日志
*/
@Before("servicePointcut()")
public void beforeAdvice(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
String className = joinPoint.getTarget().getClass().getSimpleName();
Object[] args = joinPoint.getArgs();
log.info("=== 方法执行前 ===");
log.info("类名: {}", className);
log.info("方法名: {}", methodName);
log.info("参数: {}", Arrays.toString(args));
log.info("执行时间: {}", LocalDateTime.now());
}
/**
* 后置通知:方法执行后记录日志
*/
@AfterReturning(pointcut = "servicePointcut()", returning = "result")
public void afterReturningAdvice(JoinPoint joinPoint, Object result) {
String methodName = joinPoint.getSignature().getName();
log.info("=== 方法执行后 ===");
log.info("方法名: {}", methodName);
log.info("返回值: {}", result);
log.info("执行完成时间: {}", LocalDateTime.now());
}
/**
* 异常通知:方法抛出异常时记录日志
*/
@AfterThrowing(pointcut = "servicePointcut()", throwing = "exception")
public void afterThrowingAdvice(JoinPoint joinPoint, Exception exception) {
String methodName = joinPoint.getSignature().getName();
log.error("=== 方法执行异常 ===");
log.error("方法名: {}", methodName);
log.error("异常信息: {}", exception.getMessage());
log.error("异常类型: {}", exception.getClass().getSimpleName());
}
/**
* 环绕通知:完全控制方法执行
*/
@Around("servicePointcut()")
public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
String methodName = proceedingJoinPoint.getSignature().getName();
long startTime = System.currentTimeMillis();
log.info("=== 环绕通知开始 ===");
log.info("方法名: {}", methodName);
try {
// 执行目标方法
Object result = proceedingJoinPoint.proceed();
long endTime = System.currentTimeMillis();
log.info("=== 环绕通知结束 ===");
log.info("方法名: {}", methodName);
log.info("执行耗时: {}ms", endTime - startTime);
return result;
} catch (Exception e) {
log.error("环绕通知捕获异常: {}", e.getMessage());
throw e;
}
}
}
// UserService.java - 目标服务类
@Service
@Slf4j
public class UserService {
public User getUserById(Long id) {
log.info("查询用户信息,ID: {}", id);
// 模拟数据库查询
if (id == null || id <= 0) {
throw new IllegalArgumentException("用户ID不能为空或小于等于0");
}
User user = new User();
user.setId(id);
user.setUsername("user" + id);
user.setEmail("user" + id + "@example.com");
user.setCreateTime(LocalDateTime.now());
return user;
}
public List<User> getAllUsers() {
log.info("查询所有用户信息");
List<User> users = new ArrayList<>();
for (int i = 1; i <= 3; i++) {
User user = new User();
user.setId((long) i);
user.setUsername("user" + i);
user.setEmail("user" + i + "@example.com");
user.setCreateTime(LocalDateTime.now());
users.add(user);
}
return users;
}
public User saveUser(User user) {
log.info("保存用户信息: {}", user);
if (user == null) {
throw new IllegalArgumentException("用户信息不能为空");
}
// 模拟保存操作
user.setId(System.currentTimeMillis());
user.setCreateTime(LocalDateTime.now());
return user;
}
}
// User.java - 用户实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private Long id;
private String username;
private String email;
private LocalDateTime createTime;
}
🎨 AOP切面图示
@Before
前置通知
→
Target Method
目标方法
→
@After
后置通知
@Around 环绕通知可以完全控制整个执行流程
@AfterThrowing 异常通知在方法抛出异常时执行
@AfterReturning 返回通知在方法正常返回时执行
📍 切点表达式
- execution - 匹配方法执行
- within - 匹配类型内的方法
- this - 匹配代理对象类型
- target - 匹配目标对象类型
- args - 匹配方法参数
- @annotation - 匹配注解
📢 通知类型
- @Before - 前置通知,方法执行前
- @After - 后置通知,方法执行后
- @AfterReturning - 返回通知,正常返回后
- @AfterThrowing - 异常通知,抛出异常后
- @Around - 环绕通知,完全控制执行
- @Pointcut - 切点定义,可重用
📊 性能监控切面
// PerformanceAspect.java - 性能监控切面
@Aspect
@Component
@Slf4j
public class PerformanceAspect {
/**
* 自定义注解切点
*/
@Pointcut("@annotation(cn.bugstack.springframework.boot.aop.annotation.PerformanceMonitor)")
public void performancePointcut() {}
/**
* 环绕通知:监控方法执行性能
*/
@Around("performancePointcut()")
public Object monitorPerformance(ProceedingJoinPoint joinPoint) throws Throwable {
String methodName = joinPoint.getSignature().getName();
String className = joinPoint.getTarget().getClass().getSimpleName();
// 记录开始时间
long startTime = System.currentTimeMillis();
StopWatch stopWatch = new StopWatch();
stopWatch.start();
log.info("🚀 开始执行方法: {}.{}", className, methodName);
try {
// 执行目标方法
Object result = joinPoint.proceed();
// 记录结束时间
stopWatch.stop();
long endTime = System.currentTimeMillis();
long executionTime = endTime - startTime;
// 性能分析
if (executionTime > 1000) {
log.warn("⚠️ 慢方法警告: {}.{} 执行耗时: {}ms", className, methodName, executionTime);
} else if (executionTime > 500) {
log.info("⏰ 方法执行: {}.{} 执行耗时: {}ms", className, methodName, executionTime);
} else {
log.debug("✅ 方法执行: {}.{} 执行耗时: {}ms", className, methodName, executionTime);
}
// 记录到监控系统(可集成Micrometer)
recordMetrics(className, methodName, executionTime);
return result;
} catch (Exception e) {
stopWatch.stop();
long executionTime = System.currentTimeMillis() - startTime;
log.error("❌ 方法执行异常: {}.{} 执行耗时: {}ms 异常: {}",
className, methodName, executionTime, e.getMessage());
// 记录异常指标
recordErrorMetrics(className, methodName, e);
throw e;
}
}
/**
* 记录性能指标
*/
private void recordMetrics(String className, String methodName, long executionTime) {
// 可以集成Micrometer、Prometheus等监控系统
Timer.Sample sample = Timer.start();
sample.stop(Timer.builder("method.execution.time")
.tag("class", className)
.tag("method", methodName)
.register(Metrics.globalRegistry));
}
/**
* 记录异常指标
*/
private void recordErrorMetrics(String className, String methodName, Exception e) {
Counter.builder("method.execution.error")
.tag("class", className)
.tag("method", methodName)
.tag("exception", e.getClass().getSimpleName())
.register(Metrics.globalRegistry)
.increment();
}
}
// PerformanceMonitor.java - 性能监控注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PerformanceMonitor {
/**
* 监控描述
*/
String value() default "";
/**
* 慢方法阈值(毫秒)
*/
long threshold() default 1000;
}
// 使用示例
@Service
public class OrderService {
@PerformanceMonitor("查询订单信息")
public Order getOrderById(Long orderId) {
// 模拟数据库查询
try {
Thread.sleep(800); // 模拟耗时操作
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return new Order(orderId, "ORDER-" + orderId, new BigDecimal("99.99"));
}
@PerformanceMonitor(value = "创建订单", threshold = 2000)
public Order createOrder(Order order) {
// 模拟复杂业务逻辑
try {
Thread.sleep(1500); // 模拟耗时操作
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
order.setId(System.currentTimeMillis());
return order;
}
}
🔐 权限控制切面
// SecurityAspect.java - 权限控制切面
@Aspect
@Component
@Slf4j
public class SecurityAspect {
@Autowired
private UserContext userContext;
/**
* 权限检查切点
*/
@Pointcut("@annotation(cn.bugstack.springframework.boot.aop.annotation.RequirePermission)")
public void securityPointcut() {}
/**
* 前置通知:权限检查
*/
@Before("securityPointcut() && @annotation(requirePermission)")
public void checkPermission(JoinPoint joinPoint, RequirePermission requirePermission) {
String methodName = joinPoint.getSignature().getName();
String className = joinPoint.getTarget().getClass().getSimpleName();
String[] permissions = requirePermission.value();
log.info("🔒 权限检查: {}.{} 需要权限: {}", className, methodName, Arrays.toString(permissions));
// 获取当前用户
User currentUser = userContext.getCurrentUser();
if (currentUser == null) {
log.warn("❌ 权限检查失败: 用户未登录");
throw new SecurityException("用户未登录,请先登录");
}
// 检查用户权限
Set<String> userPermissions = getUserPermissions(currentUser);
boolean hasPermission = Arrays.stream(permissions)
.anyMatch(userPermissions::contains);
if (!hasPermission) {
log.warn("❌ 权限检查失败: 用户 {} 缺少权限 {}",
currentUser.getUsername(), Arrays.toString(permissions));
throw new SecurityException("权限不足,无法访问该资源");
}
log.info("✅ 权限检查通过: 用户 {} 访问 {}.{}",
currentUser.getUsername(), className, methodName);
}
/**
* 获取用户权限
*/
private Set<String> getUserPermissions(User user) {
// 模拟从数据库或缓存中获取用户权限
Set<String> permissions = new HashSet<>();
if ("admin".equals(user.getUsername())) {
permissions.add("user:read");
permissions.add("user:write");
permissions.add("user:delete");
permissions.add("order:read");
permissions.add("order:write");
} else if ("manager".equals(user.getUsername())) {
permissions.add("user:read");
permissions.add("order:read");
permissions.add("order:write");
} else {
permissions.add("user:read");
permissions.add("order:read");
}
return permissions;
}
}
// RequirePermission.java - 权限注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequirePermission {
/**
* 需要的权限列表
*/
String[] value();
/**
* 权限检查模式:ALL-需要所有权限,ANY-需要任一权限
*/
PermissionMode mode() default PermissionMode.ANY;
}
// PermissionMode.java - 权限模式枚举
public enum PermissionMode {
ALL, // 需要所有权限
ANY // 需要任一权限
}
// 使用示例
@RestController
@RequestMapping("/api/users")
public class UserController {
@GetMapping("/{id}")
@RequirePermission("user:read")
public User getUserById(@PathVariable Long id) {
return userService.getUserById(id);
}
@PostMapping
@RequirePermission("user:write")
public User createUser(@RequestBody User user) {
return userService.saveUser(user);
}
@DeleteMapping("/{id}")
@RequirePermission(value = {"user:delete", "admin"}, mode = PermissionMode.ANY)
public void deleteUser(@PathVariable Long id) {
userService.deleteUser(id);
}
}
📊 AOP注解对比
注解类型 |
执行时机 |
参数 |
使用场景 |
@Before |
方法执行前 |
JoinPoint |
参数校验、权限检查 |
@After |
方法执行后(无论成功失败) |
JoinPoint |
资源清理、日志记录 |
@AfterReturning |
方法正常返回后 |
JoinPoint, 返回值 |
结果处理、缓存更新 |
@AfterThrowing |
方法抛出异常后 |
JoinPoint, 异常对象 |
异常处理、错误日志 |
@Around |
完全控制方法执行 |
ProceedingJoinPoint |
性能监控、事务管理 |
💡 AOP最佳实践
🎯 设计原则
单一职责 - 每个切面只处理一种横切关注点
最小侵入 - 尽量减少对业务代码的影响
性能考虑 - 避免在高频方法上使用复杂切面
异常处理 - 切面中的异常要妥善处理
测试友好 - 确保切面逻辑可以被单独测试
// AOP配置类
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AopConfig {
/**
* 配置切面执行顺序
*/
@Bean
@Order(1)
public SecurityAspect securityAspect() {
return new SecurityAspect();
}
@Bean
@Order(2)
public LoggingAspect loggingAspect() {
return new LoggingAspect();
}
@Bean
@Order(3)
public PerformanceAspect performanceAspect() {
return new PerformanceAspect();
}
}
// 切面测试
@SpringBootTest
class AopTest {
@Autowired
private UserService userService;
@Test
void testAopLogging() {
// 测试AOP日志功能
User user = userService.getUserById(1L);
assertNotNull(user);
assertEquals("user1", user.getUsername());
}
@Test
void testAopException() {
// 测试AOP异常处理
assertThrows(IllegalArgumentException.class, () -> {
userService.getUserById(-1L);
});
}
}