# 前言:从面向对象到面向切面的思维跃迁
在我刚开始接触 AOP(面向切面编程)的时候,内心充满了疑惑:为什么需要这种奇怪的编程方式?面向对象不是已经足够强大了吗?直到有一天,我面对一个典型的企业级应用场景,才真正理解了 AOP 的价值。
想象一下这个场景:你有一个包含 50 个 Service 类的系统,现在需要在每个 Service 方法执行前后记录日志、检查权限、监控性能、处理事务... 按照传统的 OOP 方式,你需要在每个方法中都重复编写这些代码。这不仅让代码变得臃肿,更违反了 DRY(Don't Repeat Yourself)原则。
AOP 的出现,就是为了解决这类横切关注点(Cross-cutting Concerns)的问题。它让我们能够将这些分散在各个模块中的相同逻辑提取出来,统一管理。这不仅仅是技术的进步,更是编程思想的一次飞跃。
今天,我想和大家分享我对 Spring AOP 的深度理解和实战经验,希望能帮助你真正掌握这门优雅的编程艺术。
# AOP 核心概念:从理论到实践
# 什么是横切关注点?
在深入 AOP 之前,我们必须先理解什么是横切关注点。让我用一个具体的例子来说明:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| @Service public class UserService { public User createUser(User user) { log.info("创建用户开始:{}", user.getName()); checkPermission("USER_CREATE"); long startTime = System.currentTimeMillis(); try { User result = userRepository.save(user); transactionManager.commit(); return result; } finally { long endTime = System.currentTimeMillis(); log.info("创建用户完成,耗时:{}ms", endTime - startTime); } } }
@Service public class OrderService { public Order createOrder(Order order) { log.info("创建订单开始:{}", order.getId()); checkPermission("ORDER_CREATE"); long startTime = System.currentTimeMillis(); try { Order result = orderRepository.save(order); transactionManager.commit(); return result; } finally { long endTime = System.currentTimeMillis(); log.info("创建订单完成,耗时:{}ms", endTime - startTime); } } }
|
看到问题了吗?日志、权限、性能监控、事务管理这些横切关注点在多个类中重复出现,让代码变得难以维护。
# AOP 的核心术语
Spring AOP 有一套完整的术语体系,理解这些术语是掌握 AOP 的第一步:
# 1. Join Point(连接点)
程序执行的特定点,如方法调用、字段访问、异常抛出等。在 Spring AOP 中,连接点通常是方法执行。
1 2 3 4 5 6
| public class UserService { public User findById(Long id) { ... } public void save(User user) { ... } public void delete(Long id) { ... } }
|
# 2. Pointcut(切入点)
匹配连接点的表达式,定义了在哪些连接点上应用通知。
1 2 3
| @Pointcut("execution(public * com.example.service.UserService.*(..))") public void userServiceMethods() {}
|
# 3. Advice(通知)
在切入点执行的代码,分为前置、后置、环绕、异常、最终五种类型。
# 4. Aspect(切面)
切入点和通知的组合,形成一个完整的横切逻辑模块。
# 5. Target Object(目标对象)
被通知的对象,也就是业务逻辑对象。
# 6. Proxy(代理)
AOP 框架创建的对象,用于拦截对目标对象的调用。
# Spring AOP 的实现原理
# 两种代理模式
Spring AOP 使用代理模式来实现切面功能,主要有两种代理方式:
# 1. JDK 动态代理
当目标对象实现了接口时,Spring 会使用 JDK 动态代理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| public interface UserService { User findById(Long id); void save(User user); }
@Service public class UserServiceImpl implements UserService { public User findById(Long id) { return userRepository.findById(id); } public void save(User user) { userRepository.save(user); } }
public class UserServiceProxy implements UserService { private UserService target; private List<Interceptor> interceptors; public User findById(Long id) { for (Interceptor interceptor : interceptors) { interceptor.before(); } try { User result = target.findById(id); for (Interceptor interceptor : interceptors) { interceptor.afterReturning(result); } return result; } catch (Exception e) { for (Interceptor interceptor : interceptors) { interceptor.afterThrowing(e); } throw e; } finally { for (Interceptor interceptor : interceptors) { interceptor.after(); } } } }
|
# 2. CGLIB 代理
当目标对象没有实现接口时,Spring 会使用 CGLIB 创建子类代理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| @Service public class UserService { public User findById(Long id) { return userRepository.findById(id); } }
public class UserService$$EnhancerBySpringCGLIB extends UserService { private UserService target; private List<Interceptor> interceptors; @Override public User findById(Long id) { MethodInterceptor interceptor = new MethodInterceptor() { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { for (Interceptor i : interceptors) { i.before(); } try { Object result = proxy.invokeSuper(obj, args); return result; } catch (Exception e) { throw e; } finally { } } }; return interceptor.intercept(this, method, args, null); } }
|
# 代理选择策略
Spring 的代理选择策略如下:
1 2 3 4 5
| @Configuration @EnableAspectJAutoProxy(proxyTargetClass = false) public class AopConfig { }
|
proxyTargetClass = false :优先使用 JDK 代理,只有当目标对象没有实现接口时才使用 CGLIBproxyTargetClass = true :强制使用 CGLIB 代理
# 切面开发的实战技巧
# 1. 基础切面定义
让我们从一个简单的日志切面开始:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
|
@Aspect @Component @Slf4j public class LoggingAspect {
@Pointcut("execution(public * com.example.service.*.*(..))") public void serviceLayer() {}
@Before("serviceLayer()") public void logBefore(JoinPoint joinPoint) { String methodName = joinPoint.getSignature().getName(); Object[] args = joinPoint.getArgs(); log.info("开始执行方法:{},参数:{}", methodName, Arrays.toString(args)); }
@AfterReturning(pointcut = "serviceLayer()", returning = "result") public void logAfterReturning(JoinPoint joinPoint, Object result) { String methodName = joinPoint.getSignature().getName(); log.info("方法执行成功:{},返回值:{}", methodName, result); }
@AfterThrowing(pointcut = "serviceLayer()", throwing = "exception") public void logAfterThrowing(JoinPoint joinPoint, Exception exception) { String methodName = joinPoint.getSignature().getName(); log.error("方法执行异常:{},异常信息:{}", methodName, exception.getMessage(), exception); } }
|
# 2. 性能监控切面
性能监控是 AOP 的经典应用场景:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
|
@Aspect @Component @Slf4j public class PerformanceAspect { private static final long PERFORMANCE_THRESHOLD = 1000;
@Pointcut("@annotation(com.example.annotation.Monitor)") public void monitorMethods() {}
@Around("monitorMethods()") public Object monitorPerformance(ProceedingJoinPoint joinPoint) throws Throwable { String methodName = joinPoint.getSignature().toShortString(); long startTime = System.currentTimeMillis(); try { Object result = joinPoint.proceed(); long endTime = System.currentTimeMillis(); long executionTime = endTime - startTime; if (executionTime > PERFORMANCE_THRESHOLD) { log.warn("性能警告:方法 {} 执行时间过长,耗时:{}ms", methodName, executionTime); } else { log.info("性能监控:方法 {} 执行完成,耗时:{}ms", methodName, executionTime); } return result; } catch (Throwable throwable) { long endTime = System.currentTimeMillis(); long executionTime = endTime - startTime; log.error("性能监控:方法 {} 执行异常,耗时:{}ms", methodName, executionTime, throwable); throw throwable; } } }
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Monitor { String value() default ""; }
|
使用这个切面:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| @Service public class UserService { @Monitor("用户查询") public User findById(Long id) { return userRepository.findById(id); } @Monitor("用户保存") public User save(User user) { return userRepository.save(user); } }
|
# 3. 权限控制切面
权限控制是另一个 AOP 的重要应用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
|
@Aspect @Component @Slf4j public class SecurityAspect { @Autowired private PermissionService permissionService;
@Pointcut("@annotation(com.example.annotation.RequirePermission)") public void permissionRequiredMethods() {}
@Before("permissionRequiredMethods() && @annotation(requirePermission)") public void checkPermission(JoinPoint joinPoint, RequirePermission requirePermission) { User currentUser = SecurityContextHolder.getCurrentUser(); if (currentUser == null) { throw new AuthenticationException("用户未登录"); } String requiredPermission = requirePermission.value(); if (!permissionService.hasPermission(currentUser, requiredPermission)) { String methodName = joinPoint.getSignature().getName(); log.warn("权限不足:用户 {} 尝试访问需要 {} 权限的方法 {}", currentUser.getId(), requiredPermission, methodName); throw new AuthorizationException("权限不足"); } log.debug("权限检查通过:用户 {} 访问方法 {}", currentUser.getId(), joinPoint.getSignature().getName()); } }
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface RequirePermission { String value(); }
|
使用权限控制:
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Service public class UserService { @RequirePermission("USER_VIEW") public User findById(Long id) { return userRepository.findById(id); } @RequirePermission("USER_DELETE") public void deleteById(Long id) { userRepository.deleteById(id); } }
|
# 4. 缓存切面
缓存是提升系统性能的重要手段:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
|
@Aspect @Component @Slf4j public class CacheAspect { @Autowired private CacheManager cacheManager;
@Pointcut("@annotation(com.example.annotation.Cacheable)") public void cacheableMethods() {}
@Around("cacheableMethods() && @annotation(cacheable)") public Object aroundCacheable(ProceedingJoinPoint joinPoint, Cacheable cacheable) throws Throwable { String cacheKey = generateCacheKey(joinPoint, cacheable); Cache cache = cacheManager.getCache(cacheable.cacheName()); Object cachedValue = cache.get(cacheKey); if (cachedValue != null) { log.debug("缓存命中:{} = {}", cacheKey, cachedValue); return cachedValue; } log.debug("缓存未命中,执行方法:{}", joinPoint.getSignature().getName()); Object result = joinPoint.proceed(); cache.put(cacheKey, result); log.debug("缓存更新:{} = {}", cacheKey, result); return result; }
private String generateCacheKey(ProceedingJoinPoint joinPoint, Cacheable cacheable) { String methodName = joinPoint.getSignature().getName(); Object[] args = joinPoint.getArgs(); if (cacheable.key().isEmpty()) { return methodName + ":" + Arrays.hashCode(args); } else { return parseSpEL(cacheable.key(), joinPoint); } }
private String parseSpEL(String expression, ProceedingJoinPoint joinPoint) { return expression.replace("#args", Arrays.toString(joinPoint.getArgs())); } }
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Cacheable { String cacheName() default "default"; String key() default ""; int expire() default 3600; }
|
# 高级切面技巧
# 1. 复杂切入点表达式
Spring AOP 支持强大的切入点表达式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| @Aspect @Component public class ComplexPointcutAspect {
@Pointcut("execution(* com.example.service.*.*(..)) && !execution(* com.example.service.*.get*(..))") public void serviceNonGetterMethods() {}
@Pointcut("@annotation(org.springframework.transaction.annotation.Transactional)") public void transactionalMethods() {}
@Pointcut("execution(* *(Long, ..))") public void methodsWithLongFirstParam() {}
@Pointcut("serviceNonGetterMethods() && transactionalMethods()") public void complexCondition() {} @Before("complexCondition()") public void beforeComplexCondition(JoinPoint joinPoint) { log.info("复杂切入点匹配:{}", joinPoint.getSignature()); } }
|
# 2. 切面优先级
当一个连接点匹配多个切面时,可以通过 @Order 注解控制执行顺序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| @Aspect @Component @Order(1) public class SecurityAspect { @Before("@annotation(RequirePermission)") public void checkPermission() { } }
@Aspect @Component @Order(2) public class LoggingAspect { @Before("@annotation(RequirePermission)") public void logBefore() { } }
@Aspect @Component @Order(3) public class PerformanceAspect { @Around("@annotation(Monitor)") public Object monitor() { } }
|
# 3. 引入增强(Introduction)
引入增强可以为现有类添加新的接口和方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
|
@Aspect @Component public class AuditIntroduction {
public interface Auditable { void audit(String operation); List<String> getAuditLogs(); }
public static class AuditableImpl implements Auditable { private List<String> auditLogs = new ArrayList<>(); @Override public void audit(String operation) { auditLogs.add(LocalDateTime.now() + ": " + operation); } @Override public List<String> getAuditLogs() { return new ArrayList<>(auditLogs); } }
@DeclareParents( value = "com.example.service.*+", // 匹配所有Service实现类 defaultImpl = AuditableImpl.class // 默认实现 ) public static Auditable auditable; }
|
使用引入增强:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| @Service public class UserService { public User save(User user) { return userRepository.save(user); } }
@Controller public class UserController { @Autowired private UserService userService; public void saveUser(User user) { userService.save(user); Auditable auditable = (Auditable) userService; auditable.audit("保存用户:" + user.getName()); List<String> logs = auditable.getAuditLogs(); logs.forEach(System.out::println); } }
|
# AOP 最佳实践
# 1. 合理使用 AOP
适合使用 AOP 的场景:
不适合使用 AOP 的场景:
- 核心业务逻辑
- 需要细粒度控制的逻辑
- 性能敏感的代码(AOP 有一定开销)
# 2. 避免过度使用 AOP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| @Aspect @Component public class OverUsedAspect { @Before("execution(* com.example.service.*.*(..))") public void before1() { } @Before("execution(* com.example.service.*.*(..))") public void before2() { } @After("execution(* com.example.service.*.*(..))") public void after1() { } @After("execution(* com.example.service.*.*(..))") public void after2() { } }
@Aspect @Component public class WellUsedAspect { @Around("execution(* com.example.service.*.*(..))") public Object around(ProceedingJoinPoint joinPoint) throws Throwable { beforeLogic(); try { Object result = joinPoint.proceed(); afterLogic(result); return result; } catch (Exception e) { exceptionLogic(e); throw e; } } }
|
# 3. 性能优化
AOP 会带来一定的性能开销,需要注意优化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| @Aspect @Component public class PerformanceOptimizedAspect { private final Map<String, Boolean> pointcutCache = new ConcurrentHashMap<>();
@Pointcut("execution(public * com.example.service.*.*(..))") public void serviceMethods() {}
@Around("serviceMethods()") public Object optimizedAround(ProceedingJoinPoint joinPoint) throws Throwable { String methodName = joinPoint.getSignature().getName(); if (isFastPathMethod(methodName)) { return joinPoint.proceed(); } return processWithAop(joinPoint); } private boolean isFastPathMethod(String methodName) { return pointcutCache.computeIfAbsent(methodName, name -> name.startsWith("get") || name.startsWith("is")); } }
|
# 4. 测试 AOP 切面
切面也需要单元测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| @SpringBootTest public class LoggingAspectTest { @Autowired private UserService userService; @MockBean private UserRepository userRepository; @Test public void testLoggingAspect() { User user = new User(1L, "test"); when(userRepository.findById(1L)).thenReturn(user); User result = userService.findById(1L); assertEquals(user, result); } }
|
# 常见问题与解决方案
# 1. 代理失效问题
有时候 AOP 不生效,常见原因包括:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| @Service public class UserService { public void methodA() { methodB(); } @Transactional public void methodB() { } }
@Service public class UserService { @Autowired private UserService self; public void methodA() { self.methodB(); } @Transactional public void methodB() { } }
@Service public class UserService { public void methodA() { ((UserService) AopContext.currentProxy()).methodB(); } @Transactional public void methodB() { } }
|
# 2. 切入点表达式错误
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Pointcut("execution(* com.example.servicee.*.*(..))")
@Pointcut("execution(private * com.example.service.*.*(..))")
@Pointcut("execution(public * com.example.service.*.*(..))") public void publicServiceMethods() {}
@Pointcut("within(com.example.service.*)") public void withinServicePackage() {}
|
# 3. 循环依赖问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| @Aspect @Component public class AspectA { @Autowired private ServiceB serviceB; }
@Service public class ServiceB { @Autowired private AspectA aspectA; }
@Aspect @Component public class AspectA { @Autowired @Lazy private ServiceB serviceB; }
|
# 总结:AOP 的艺术之美
AOP 不仅仅是一种技术,更是一种编程思想的升华。它让我们能够:
- 关注点分离:将横切关注点从业务逻辑中分离出来
- 代码复用:避免在多个地方重复编写相同的逻辑
- 维护性提升:修改横切逻辑只需要在一个地方进行
- 非侵入性:不需要修改原有的业务代码
但也要记住,AOP 不是银弹。过度使用 AOP 会让代码变得难以理解和调试。合理使用 AOP,才能发挥它的最大价值。
在学习 AOP 的过程中,我最大的感悟是:技术本身并不复杂,复杂的是如何正确地使用它。理解了 AOP 的核心思想,掌握了基本的实现原理,你就能在实际项目中游刃有余地运用这门技术。