拦截器模式:Spring AOP 背后的设计智慧

说实话,拦截器模式是我在项目中用得最多的模式之一。你想想看,一个系统上线后,总得加点日志、做做权限校验、搞搞事务管理吧?这些横切关注点如果散落在业务代码里,那维护起来简直是一场噩梦。拦截器模式就是专门解决这个问题的。

我记得刚做架构那会儿,接手过一个老系统。业务逻辑和日志、权限、事务全混在一起,一个方法几百行,改个需求得翻半天代码。后来我重构时引入了拦截器模式,代码瞬间清爽了。说白了,拦截器就是让你在不修改原有业务代码的前提下,给方法调用前后插入一些额外操作。

什么是拦截器模式?

拦截器模式的核心思想其实很简单:在目标方法执行前后,插入一些预处理和后处理逻辑。它由三个关键角色组成:

  • 拦截器接口:定义了拦截行为的契约,比如 before()、after()、around() 等方法
  • 目标对象:真正执行业务逻辑的对象
  • 代理链:将多个拦截器串联起来,形成一个调用链条

我习惯把拦截器想象成「洋葱皮」。你调用目标方法时,会一层层穿过拦截器,每层都可以在调用前后做点手脚。嗯,这个比喻很形象。

核心要点:拦截器模式实现了关注点分离,让业务逻辑和非业务逻辑解耦。这是 AOP(面向切面编程)的底层实现机制。

拦截器模式的结构

我们先看看拦截器模式的标准类图。我个人习惯用这种图来理清思路:

拦截器模式结构图 «接口» Interceptor + before() + after() LogInterceptor + before() // 记录日志 AuthInterceptor + before() // 权限校验 TargetObject + businessMethod() // 核心业务逻辑 ProxyChain + interceptors: List + proceed() Client // 调用代理链 使用 多个拦截器组成代理链,依次执行 before/after 逻辑 最终调用目标对象的业务方法

你看这个图,客户端不直接调用目标对象,而是通过代理链。代理链里维护了一组拦截器,每个拦截器都可以在方法调用前后插入逻辑。这就是 AOP 的底层实现机制。

Java 代码实现

我们先从最基础的拦截器接口开始。我写代码喜欢先定义契约:

// 拦截器接口
public interface Interceptor {
    // 前置处理
    boolean before(Object target, Method method, Object[] args);
    
    // 后置处理
    void after(Object target, Method method, Object[] args, Object result);
    
    // 异常处理
    void exception(Object target, Method method, Object[] args, Throwable e);
}

接下来是代理链的实现。这里我用了责任链模式的思想,每个拦截器执行完 before 后,决定是否继续往下走:

public class ProxyChain {
    private List<Interceptor> interceptors;
    private Object target;
    private int currentIndex = 0;
    
    public ProxyChain(Object target, List<Interceptor> interceptors) {
        this.target = target;
        this.interceptors = interceptors;
    }
    
    public Object proceed(Method method, Object[] args) throws Throwable {
        // 所有拦截器执行完毕,调用目标方法
        if (currentIndex == interceptors.size()) {
            return method.invoke(target, args);
        }
        
        Interceptor interceptor = interceptors.get(currentIndex++);
        
        // 执行前置处理
        boolean proceed = interceptor.before(target, method, args);
        if (!proceed) {
            return null; // 拦截器阻止了后续执行
        }
        
        Object result = null;
        try {
            // 递归调用下一个拦截器或目标方法
            result = proceed(method, args);
        } catch (Throwable e) {
            interceptor.exception(target, method, args, e);
            throw e;
        } finally {
            interceptor.after(target, method, args, result);
        }
        
        return result;
    }
}

个人经验:我在实现代理链时,喜欢用递归而不是循环。递归的方式更符合「洋葱模型」的直觉,每个拦截器都能控制调用链的走向。不过要注意递归深度,如果拦截器太多,可以考虑用栈来优化。

写一个具体的拦截器看看效果:

public class LogInterceptor implements Interceptor {
    @Override
    public boolean before(Object target, Method method, Object[] args) {
        System.out.println("[日志] 调用方法: " + method.getName() + 
                           ",参数: " + Arrays.toString(args));
        return true; // 继续执行
    }
    
    @Override
    public void after(Object target, Method method, Object[] args, Object result) {
        System.out.println("[日志] 方法返回: " + result);
    }
    
    @Override
    public void exception(Object target, Method method, Object[] args, Throwable e) {
        System.err.println("[日志] 方法异常: " + e.getMessage());
    }
}

C++ 代码实现

C++ 的实现思路和 Java 类似,但要注意资源管理和模板编程。我个人在 C++ 项目中更喜欢用模板来避免虚函数开销:

#include <iostream>
#include <vector>
#include <functional>
#include <memory>

// 拦截器接口(抽象类)
class Interceptor {
public:
    virtual ~Interceptor() = default;
    virtual bool before(const std::string& methodName, 
                        const std::vector<std::any>& args) = 0;
    virtual void after(const std::string& methodName, 
                       const std::any& result) = 0;
    virtual void exception(const std::string& methodName, 
                           const std::exception& e) = 0;
};

// 代理链
class ProxyChain {
private:
    std::vector<std::shared_ptr<Interceptor>> interceptors_;
    size_t currentIndex_ = 0;
    std::function<std::any()> targetMethod_;
    
public:
    ProxyChain(std::function<std::any()> targetMethod,
               std::vector<std::shared_ptr<Interceptor>> interceptors)
        : targetMethod_(std::move(targetMethod))
        , interceptors_(std::move(interceptors)) {}
    
    std::any proceed(const std::string& methodName,
                     const std::vector<std::any>& args) {
        if (currentIndex_ == interceptors_.size()) {
            return targetMethod_();
        }
        
        auto& interceptor = interceptors_[currentIndex_++];
        
        if (!interceptor->before(methodName, args)) {
            return std::any(); // 被拦截
        }
        
        std::any result;
        try {
            result = proceed(methodName, args);
        } catch (const std::exception& e) {
            interceptor->exception(methodName, e);
            throw;
        }
        
        interceptor->after(methodName, result);
        return result;
    }
};

避坑指南:我曾经在 C++ 项目中犯过一个错误——拦截器里抛了异常但没有正确处理,导致整个调用链中断。后来我规定:拦截器的 before() 方法如果返回 false,表示拦截;如果抛出异常,表示严重错误,应该终止调用链并向上传播。这两种语义要区分清楚。

Spring AOP 中的拦截器

说到拦截器模式,就不得不提 Spring AOP。Spring AOP 本质上就是拦截器模式的一种实现。它提供了几个核心概念:

Spring AOP 概念 对应拦截器模式角色 说明
Advice(通知) 拦截器 定义在目标方法前后执行的逻辑
Pointcut(切点) 拦截条件 决定哪些方法需要被拦截
Advisor(通知器) 拦截器 + 条件 将 Advice 和 Pointcut 组合在一起
AopProxy(代理) 代理链 生成代理对象,管理拦截器链

Spring 中常用的拦截器类型有:

  • MethodInterceptor:最核心的拦截器接口,通过 Invocation.proceed() 控制调用链
  • BeforeAdvice:前置通知,在目标方法前执行
  • AfterReturningAdvice:后置通知,在目标方法正常返回后执行
  • ThrowsAdvice:异常通知,在目标方法抛出异常时执行
  • AroundAdvice:环绕通知,可以完全控制目标方法的执行

我举个例子,Spring 中自定义一个拦截器其实很简单:

@Component
public class PerformanceInterceptor implements MethodInterceptor {
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        long start = System.currentTimeMillis();
        
        try {
            Object result = invocation.proceed(); // 继续调用链
            return result;
        } finally {
            long duration = System.currentTimeMillis() - start;
            System.out.println("方法 " + invocation.getMethod().getName() 
                               + " 执行耗时: " + duration + "ms");
        }
    }
}

关键理解:Spring AOP 的 MethodInterceptor.invoke() 方法,本质上就是代理链的 proceed() 方法。你调用 invocation.proceed() 时,它会继续执行下一个拦截器,直到最终调用目标方法。这就是「洋葱模型」的核心。

实际项目中的应用场景

我在项目中用过拦截器模式的地方太多了,挑几个典型的说说:

  1. 日志记录:统一记录方法调用日志,包括入参、出参、耗时
  2. 权限校验:在方法执行前检查用户是否有权限
  3. 事务管理:在方法执行前开启事务,执行后提交或回滚
  4. 缓存处理:在方法执行前检查缓存,执行后更新缓存
  5. 参数校验:统一校验方法参数是否合法
  6. 性能监控:统计方法执行时间,上报到监控系统

你想想看,如果没有拦截器模式,这些逻辑就得散落在每个业务方法里。改个日志格式要改几百个方法,那画面太美我不敢看。

我的建议:设计拦截器时,尽量让每个拦截器只做一件事。比如日志拦截器只负责日志,权限拦截器只负责权限。这样组合起来更灵活,也更容易测试。我曾经见过一个拦截器里既写日志又做权限还管事务,那代码简直没法看。

拦截器模式还有一个好处:你可以动态地添加或移除拦截器,而不影响业务代码。比如线上出了性能问题,你可以动态加一个性能监控拦截器,排查完再移除。这种灵活性在大型系统中特别有用。

好了,关于拦截器模式就聊这么多。记住一句话:拦截器模式是 AOP 的基石,而 AOP 是让代码保持干净的利器。下次你写 Spring 的 @Around 注解时,想想背后的拦截器模式,你会对它有更深的理解。