Spring AOP面向切面编程原理与实战详解

最近在重构一个Spring Boot项目时,需要对所有接口添加统一的日志记录和性能监控,使用传统方式需要修改每个方法,工作量巨大且代码冗余。Spring AOP(面向切面编程)完美解决了这个问题,本文将深入剖析Spring AOP的使用方式和底层原理。

AOP基本概念

AOP(Aspect-Oriented Programming,面向切面编程)是对OOP(面向对象编程)的补充,它将系统中的横切关注点(如日志、事务、权限等)从业务逻辑中分离出来,实现关注点分离。

核心术语

在深入Spring AOP之前,先理解几个核心概念:

  • Aspect(切面):横切关注点的模块化,比如日志模块、事务管理模块
  • Join Point(连接点):程序执行过程中的某个点,Spring AOP中特指方法执行
  • Advice(通知):切面在特定连接点执行的动作,分为前置、后置、环绕、异常、最终通知
  • Pointcut(切入点):匹配连接点的表达式,决定哪些方法需要被增强
  • Target(目标对象):被AOP代理的原始对象
  • Proxy(代理):AOP框架创建的对象,包含了原始对象的功能和增强功能
  • Weaving(织入):将切面应用到目标对象创建代理对象的过程

Spring AOP快速上手

引入依赖

xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

基础使用示例

创建一个简单的日志切面:

java
@Aspect
@Component
@Slf4j
public class LoggingAspect {

    // 定义切入点:匹配controller包下所有类的所有方法
    @Pointcut("execution(* com.example.controller.*.*(..))")
    public void controllerMethods() {}

    // 前置通知:方法执行前记录
    @Before("controllerMethods()")
    public void logBefore(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        log.info("方法 {} 开始执行,参数:{}", methodName, Arrays.toString(args));
    }

    // 后置返回通知:方法正常返回后
    @AfterReturning(pointcut = "controllerMethods()", returning = "result")
    public void logAfterReturning(JoinPoint joinPoint, Object result) {
        String methodName = joinPoint.getSignature().getName();
        log.info("方法 {} 执行完成,返回值:{}", methodName, result);
    }

    // 异常通知:方法抛出异常时
    @AfterThrowing(pointcut = "controllerMethods()", throwing = "ex")
    public void logAfterThrowing(JoinPoint joinPoint, Exception ex) {
        String methodName = joinPoint.getSignature().getName();
        log.error("方法 {} 执行异常:{}", methodName, ex.getMessage());
    }
}

环绕通知实现性能监控

环绕通知是最强大的通知类型,可以完全控制方法的执行:

java
@Aspect
@Component
public class PerformanceAspect {

    @Around("@annotation(com.example.annotation.MonitorPerformance)")
    public Object monitorPerformance(ProceedingJoinPoint pjp) throws Throwable {
        long startTime = System.currentTimeMillis();

        // 获取方法信息
        String className = pjp.getTarget().getClass().getSimpleName();
        String methodName = pjp.getSignature().getName();

        try {
            // 执行目标方法
            Object result = pjp.proceed();

            long elapsedTime = System.currentTimeMillis() - startTime;
            if (elapsedTime > 1000) {
                log.warn("{}.{} 执行时间过长:{}ms", className, methodName, elapsedTime);
            } else {
                log.info("{}.{} 执行时间:{}ms", className, methodName, elapsedTime);
            }

            return result;
        } catch (Exception e) {
            long elapsedTime = System.currentTimeMillis() - startTime;
            log.error("{}.{} 执行异常,耗时:{}ms", className, methodName, elapsedTime, e);
            throw e;
        }
    }
}

自定义注解:

java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MonitorPerformance {
    String value() default "";
}

使用示例:

java
@RestController
@RequestMapping("/api/users")
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/{id}")
    @MonitorPerformance
    public User getUser(@PathVariable Long id) {
        return userService.findById(id);
    }
}

切入点表达式详解

Spring AOP支持多种切入点表达式,最常用的是execution表达式:

execution表达式语法

text
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)

示例:

java
// 匹配所有public方法
@Pointcut("execution(public * *(..))")

// 匹配指定包下所有类的所有方法
@Pointcut("execution(* com.example.service.*.*(..))")

// 匹配指定类的所有方法
@Pointcut("execution(* com.example.service.UserService.*(..))")

// 匹配所有返回User类型的方法
@Pointcut("execution(com.example.model.User *(..))")

// 匹配所有以save开头的方法
@Pointcut("execution(* save*(..))")

// 匹配第一个参数为Long类型的方法
@Pointcut("execution(* *(Long, ..))")

其他切入点表达式

java
@Aspect
@Component
public class AdvancedPointcutAspect {

    // within:匹配特定类型内的方法
    @Pointcut("within(com.example.service.*)")
    public void inServiceLayer() {}

    // @within:匹配标注了特定注解的类
    @Pointcut("@within(org.springframework.stereotype.Service)")
    public void serviceAnnotated() {}

    // @annotation:匹配标注了特定注解的方法
    @Pointcut("@annotation(org.springframework.transaction.annotation.Transactional)")
    public void transactionalMethods() {}

    // args:匹配特定参数类型
    @Pointcut("args(String, Long)")
    public void stringLongArgs() {}

    // @args:匹配参数标注了特定注解
    @Pointcut("@args(com.example.annotation.Validated)")
    public void validatedArgs() {}

    // bean:匹配特定名称的Bean
    @Pointcut("bean(*Service)")
    public void serviceBeans() {}

    // 组合切入点
    @Pointcut("inServiceLayer() && transactionalMethods()")
    public void transactionalServiceMethods() {}
}

实战案例

统一异常处理与日志记录

java
@Aspect
@Component
@Slf4j
public class ExceptionHandlingAspect {

    @Around("@within(org.springframework.web.bind.annotation.RestController)")
    public Object handleException(ProceedingJoinPoint pjp) throws Throwable {
        String methodName = pjp.getSignature().toShortString();

        try {
            return pjp.proceed();
        } catch (BusinessException e) {
            // 业务异常,记录警告日志
            log.warn("业务异常 in {}: {}", methodName, e.getMessage());
            return ResponseEntity.badRequest().body(
                Map.of("error", e.getMessage(), "code", e.getCode())
            );
        } catch (Exception e) {
            // 系统异常,记录错误日志
            log.error("系统异常 in {}", methodName, e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(
                Map.of("error", "系统内部错误", "code", "SYSTEM_ERROR")
            );
        }
    }
}

方法级缓存实现

java
@Aspect
@Component
public class CachingAspect {

    private final Map<String, Object> cache = new ConcurrentHashMap<>();

    @Around("@annotation(cacheable)")
    public Object cache(ProceedingJoinPoint pjp, Cacheable cacheable) throws Throwable {
        // 生成缓存key
        String key = generateKey(pjp, cacheable.key());

        // 检查缓存
        if (cache.containsKey(key)) {
            log.debug("缓存命中: {}", key);
            return cache.get(key);
        }

        // 执行方法
        Object result = pjp.proceed();

        // 存入缓存
        if (result != null) {
            cache.put(key, result);
            log.debug("缓存存储: {}", key);

            // 设置过期(简化示例,实际应使用更完善的缓存方案)
            if (cacheable.expire() > 0) {
                scheduleEviction(key, cacheable.expire());
            }
        }

        return result;
    }

    private String generateKey(ProceedingJoinPoint pjp, String keyExpression) {
        if (StringUtils.hasText(keyExpression)) {
            // 使用SpEL解析key表达式
            return parseSpel(keyExpression, pjp);
        }

        // 默认使用类名+方法名+参数
        return pjp.getTarget().getClass().getName() + "." +
               pjp.getSignature().getName() +
               Arrays.toString(pjp.getArgs());
    }

    private void scheduleEviction(String key, long seconds) {
        ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
        executor.schedule(() -> cache.remove(key), seconds, TimeUnit.SECONDS);
    }
}

分布式锁实现

java
@Aspect
@Component
public class DistributedLockAspect {

    @Autowired
    private RedissonClient redissonClient;

    @Around("@annotation(distributedLock)")
    public Object lock(ProceedingJoinPoint pjp, DistributedLock distributedLock) throws Throwable {
        String lockKey = resolveLockKey(pjp, distributedLock);
        RLock lock = redissonClient.getLock(lockKey);

        boolean acquired = false;
        try {
            // 尝试获取锁
            acquired = lock.tryLock(
                distributedLock.waitTime(),
                distributedLock.leaseTime(),
                TimeUnit.SECONDS
            );

            if (!acquired) {
                throw new BusinessException("获取锁失败,请稍后重试");
            }

            log.info("获取分布式锁成功: {}", lockKey);
            return pjp.proceed();

        } finally {
            if (acquired && lock.isHeldByCurrentThread()) {
                lock.unlock();
                log.info("释放分布式锁: {}", lockKey);
            }
        }
    }

    private String resolveLockKey(ProceedingJoinPoint pjp, DistributedLock annotation) {
        String key = annotation.key();
        if (key.contains("#")) {
            // 解析SpEL表达式
            return parseSpelKey(key, pjp);
        }
        return "lock:" + key;
    }
}

Spring AOP原理

代理模式基础

Spring AOP基于代理模式实现,主要有两种代理方式:

JDK动态代理

适用于实现了接口的类:

java
public class JdkProxyExample {

    interface UserService {
        void save(String user);
    }

    static class UserServiceImpl implements UserService {
        @Override
        public void save(String user) {
            System.out.println("保存用户: " + user);
        }
    }

    static class LoggingHandler implements InvocationHandler {
        private final Object target;

        public LoggingHandler(Object target) {
            this.target = target;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("方法执行前: " + method.getName());
            Object result = method.invoke(target, args);
            System.out.println("方法执行后: " + method.getName());
            return result;
        }
    }

    public static void main(String[] args) {
        UserService target = new UserServiceImpl();
        UserService proxy = (UserService) Proxy.newProxyInstance(
            target.getClass().getClassLoader(),
            target.getClass().getInterfaces(),
            new LoggingHandler(target)
        );

        proxy.save("张三");
    }
}

CGLIB代理

适用于没有实现接口的类:

java
public class CglibProxyExample {

    static class UserService {
        public void save(String user) {
            System.out.println("保存用户: " + user);
        }
    }

    static class LoggingInterceptor implements MethodInterceptor {
        @Override
        public Object intercept(Object obj, Method method, Object[] args,
                              MethodProxy proxy) throws Throwable {
            System.out.println("方法执行前: " + method.getName());
            Object result = proxy.invokeSuper(obj, args);
            System.out.println("方法执行后: " + method.getName());
            return result;
        }
    }

    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(UserService.class);
        enhancer.setCallback(new LoggingInterceptor());

        UserService proxy = (UserService) enhancer.create();
        proxy.save("李四");
    }
}

Spring AOP代理创建流程

Spring通过DefaultAopProxyFactory决定使用哪种代理方式:

java
public class DefaultAopProxyFactory implements AopProxyFactory {

    @Override
    public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
        // 如果满足以下条件之一,使用CGLIB代理:
        // 1. 配置强制使用CGLIB(proxy-target-class=true)
        // 2. 目标类没有实现接口
        // 3. 只代理了SpringProxy接口
        if (config.isOptimize() || config.isProxyTargetClass() ||
            hasNoUserSuppliedProxyInterfaces(config)) {

            Class<?> targetClass = config.getTargetClass();
            if (targetClass == null) {
                throw new AopConfigException("TargetSource cannot determine target class");
            }

            // 如果目标类是接口或者是代理类,使用JDK代理
            if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
                return new JdkDynamicAopProxy(config);
            }

            // 使用CGLIB代理
            return new ObjenesisCglibAopProxy(config);
        } else {
            // 使用JDK代理
            return new JdkDynamicAopProxy(config);
        }
    }
}

@AspectJ注解处理流程

Spring容器启动时,AnnotationAwareAspectJAutoProxyCreator负责处理@AspectJ注解:

java
public class AnnotationAwareAspectJAutoProxyCreator extends AspectJAwareAdvisorAutoProxyCreator {

    @Override
    protected List<Advisor> findCandidateAdvisors() {
        // 调用父类方法查找所有Advisor
        List<Advisor> advisors = super.findCandidateAdvisors();

        // 查找@AspectJ注解的切面
        if (this.aspectJAdvisorsBuilder != null) {
            advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
        }

        return advisors;
    }

    // 构建切面通知
    public List<Advisor> buildAspectJAdvisors() {
        List<String> aspectNames = this.aspectBeanNames;

        if (aspectNames == null) {
            synchronized (this) {
                aspectNames = this.aspectBeanNames;
                if (aspectNames == null) {
                    List<Advisor> advisors = new ArrayList<>();
                    aspectNames = new ArrayList<>();

                    // 获取所有Bean名称
                    String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
                        this.beanFactory, Object.class, true, false);

                    for (String beanName : beanNames) {
                        Class<?> beanType = this.beanFactory.getType(beanName);

                        // 判断是否为@Aspect注解的类
                        if (this.advisorFactory.isAspect(beanType)) {
                            aspectNames.add(beanName);

                            // 获取切面元数据
                            AspectMetadata amd = new AspectMetadata(beanType, beanName);

                            // 创建Advisor
                            List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(
                                new BeanFactoryAspectInstanceFactory(
                                    this.beanFactory, beanName));

                            advisors.addAll(classAdvisors);
                        }
                    }

                    this.aspectBeanNames = aspectNames;
                    return advisors;
                }
            }
        }

        return Collections.emptyList();
    }
}

通知执行链

当代理对象的方法被调用时,会构建一个拦截器链:

java
public class ReflectiveMethodInvocation implements ProxyMethodInvocation {

    protected final Object proxy;
    protected final Object target;
    protected final Method method;
    protected Object[] arguments;
    protected final List<?> interceptorsAndDynamicMethodMatchers;
    private int currentInterceptorIndex = -1;

    @Override
    public Object proceed() throws Throwable {
        // 所有拦截器执行完毕,执行目标方法
        if (this.currentInterceptorIndex ==
            this.interceptorsAndDynamicMethodMatchers.size() - 1) {
            return invokeJoinpoint();
        }

        // 获取下一个拦截器
        Object interceptorOrInterceptionAdvice =
            this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);

        if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
            // 动态匹配
            InterceptorAndDynamicMethodMatcher dm =
                (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;

            if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {
                return dm.interceptor.invoke(this);
            } else {
                // 跳过不匹配的拦截器
                return proceed();
            }
        } else {
            // 执行拦截器
            return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
        }
    }
}

性能优化

合理使用切入点表达式

java
// 不推荐:范围太广
@Pointcut("execution(* *(..))")

// 推荐:精确匹配
@Pointcut("execution(* com.example.service.*Service.*(..))")

避免在通知中执行耗时操作

java
@Aspect
@Component
public class AsyncLoggingAspect {

    @Autowired
    private AsyncTaskExecutor taskExecutor;

    @AfterReturning("@annotation(Loggable)")
    public void logAsync(JoinPoint joinPoint) {
        // 异步执行耗时操作
        taskExecutor.execute(() -> {
            // 耗时的日志处理
            processLog(joinPoint);
        });
    }
}

使用编译时织入提升性能

对于性能敏感的场景,可以使用AspectJ编译时织入(CTW)或加载时织入(LTW):

xml
<!-- 启用AspectJ编译时织入 -->
<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>aspectj-maven-plugin</artifactId>
    <version>1.14.0</version>
    <configuration>
        <complianceLevel>11</complianceLevel>
        <source>11</source>
        <target>11</target>
        <showWeaveInfo>true</showWeaveInfo>
        <aspectLibraries>
            <aspectLibrary>
                <groupId>org.springframework</groupId>
                <artifactId>spring-aspects</artifactId>
            </aspectLibrary>
        </aspectLibraries>
    </configuration>
    <executions>
        <execution>
            <goals>
                <goal>compile</goal>
            </goals>
        </execution>
    </executions>
</plugin>

常见问题与解决方案

自调用失效问题

java
@Service
public class UserService {

    @Transactional
    public void saveUser(User user) {
        // 直接调用本类方法,AOP不生效
        sendNotification(user); // 错误!

        // 正确方式1:注入自身代理
        ((UserService) AopContext.currentProxy()).sendNotification(user);

        // 正确方式2:将方法移到其他类
    }

    @Async
    public void sendNotification(User user) {
        // 发送通知
    }
}

// 启用暴露代理
@EnableAspectJAutoProxy(exposeProxy = true)

final方法和类的问题

CGLIB无法代理final方法和final类:

java
// 错误:final类无法被CGLIB代理
@Service
public final class UserService {
    // ...
}

// 错误:final方法无法被增强
@Service
public class UserService {
    public final void save() {
        // ...
    }
}

切面执行顺序

使用@Order注解控制多个切面的执行顺序:

java
@Aspect
@Component
@Order(1) // 数字越小,优先级越高
public class SecurityAspect {
    // 安全检查
}

@Aspect
@Component
@Order(2)
public class LoggingAspect {
    // 日志记录
}

@Aspect
@Component
@Order(3)
public class CachingAspect {
    // 缓存处理
}

写在最后

AOP适合处理那些与业务逻辑正交的横切关注点,对于核心业务逻辑,还是应该使用传统的OOP方式来组织代码。

Web后端服务身份认证方案汇总与个人思考
Valaxy测试页