在面试或者团队技术分享时,每当聊到 Spring AOP,大家张嘴闭嘴就是“动态代理”、“JDK 还是 CGLIB”、“前置后置环绕通知”。
但如果我递给你一张白纸,或者打开一个纯净的 Java 控制台工程,不依赖任何 Spring 框架的 Jar 包,要求你:“用纯原生 Java 代码,实现一个支持多个拦截器顺序嵌套执行的 AOP 切面链”,很多平时写惯了 Spring 注解的同学立刻就会卡壳。
Spring AOP 的底层设计固然精巧庞大,但它的灵魂内核其实非常精简:无非就是动态代理(Dynamic Proxy) + 责任链模式(Chain of Responsibility)。
今天我们就剥离掉 Spring 所有的外壳(Bean 生命周期、AspectJ 表达式解析、配置加载),用不到 100 行纯粹的 Java 代码,从零手写一个五脏俱全、支持链式拦截的 Mini AOP 框架。
一、Mini AOP 的核心契约设计
要实现链式拦截,我们只需要三个核心接口:
MethodInvocation:方法调用上下文,负责驱动拦截链一步步向前推进;Interceptor:拦截器接口,定义切面逻辑,类似于 Spring 的MethodInterceptor;ProxyFactory:工厂类,通过 JDK 动态代理将目标对象与拦截器链组装在一起。
[Client Method Call] | v [JDK Dynamic Proxy] | v [ReflectiveMethodInvocation.proceed()] | +---> Interceptor 1 (Before) | | | v | Interceptor 2 (Before) | | | v | [Target Method Actual Invoke] | | | v | Interceptor 2 (After) | | +---> Interceptor 1 (After)二、百行源码纯手写实战
我们不需要借助任何第三方依赖,直接新建一个纯粹的 Java 源文件。
1. 拦截器接口与调用链推进器
首先定义方法调用的执行上下文契约:
package com.yali.miniaop; import java.lang.reflect.Method; import java.util.List; // 1. 方法调用上下文 public interface MethodInvocation { Method getMethod(); Object[] getArguments(); Object getTarget(); // 驱动责任链的核心方法 Object proceed() throws Throwable; } // 2. 环绕拦截器接口 @FunctionalInterface public interface Interceptor { Object invoke(MethodInvocation invocation) throws Throwable; }紧接着是整个 AOP 最精妙的灵魂类——ReflectiveMethodInvocation。它通过一个递增的游标(Index),像剥洋葱一样逐层执行拦截器:
package com.yali.miniaop; import java.lang.reflect.Method; import java.util.List; public class ReflectiveMethodInvocation implements MethodInvocation { private final Object target; private final Method method; private final Object[] arguments; private final List<Interceptor> interceptors; private int currentInterceptorIndex = -1; public ReflectiveMethodInvocation(Object target, Method method, Object[] arguments, List<Interceptor> interceptors) { this.target = target; this.method = method; this.arguments = arguments; this.interceptors = interceptors; } @Override public Object proceed() throws Throwable { // 如果所有拦截器都已执行完毕,最终反射调用目标对象的真实方法! if (this.currentInterceptorIndex == this.interceptors.size() - 1) { return this.method.invoke(this.target, this.arguments); } // 游标递增,取出下一个拦截器 Interceptor interceptor = this.interceptors.get(++this.currentInterceptorIndex); // 把自身 (MethodInvocation) 传给拦截器,拦截器内部决定何时再次调用 proceed() return interceptor.invoke(this); } @Override public Method getMethod() { return method; } @Override public Object[] getArguments() { return arguments; } @Override public Object getTarget() { return target; } }2. 代理工厂:组装动态代理
通过 JDK 原生的java.lang.reflect.Proxy,将目标对象包装为代理对象:
package com.yali.miniaop; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.ArrayList; import java.util.List; public class MiniProxyFactory { private Object target; private final List<Interceptor> interceptors = new ArrayList<>(); public void setTarget(Object target) { this.target = target; } public void addInterceptor(Interceptor interceptor) { this.interceptors.add(interceptor); } @SuppressWarnings("unchecked") public <T> T getProxy() { return (T) Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), (proxy, method, args) -> { // 每次方法调用,实例化一个全新的调用链推进器 MethodInvocation invocation = new ReflectiveMethodInvocation( target, method, args, interceptors ); return invocation.proceed(); } ); } }以上全部代码加起来,刚好在 80 行左右,已经构筑起了一套完整的 AOP 运行时核心!
三、实战演习:织入耗时监控与安全权限切面
我们来定义一个业务接口,并为它挂上两个独立的拦截器,验证洋葱模型的执行时序。
package com.yali.miniaop; // 业务接口与实现 interface PaymentService { void pay(String orderId, double amount); } class PaymentServiceImpl implements PaymentService { @Override public void pay(String orderId, double amount) { System.out.println(">>> 核心业务执行:正在扣除账户金额: " + amount + ", 订单号: " + orderId); } } // 测试主程序 public class MiniAopDemo { public static void main(String[] args) { // 1. 准备目标对象 PaymentService targetService = new PaymentServiceImpl(); // 2. 准备拦截器 1:耗时统计 Interceptor timeCostInterceptor = invocation -> { long start = System.currentTimeMillis(); System.out.println("[TimeCost] 方法调用前置计时开始: " + invocation.getMethod().getName()); try { return invocation.proceed(); // 推进到下一个拦截器 } finally { System.out.println("[TimeCost] 方法调用结束,耗时: " + (System.currentTimeMillis() - start) + "ms"); } }; // 3. 准备拦截器 2:权限与日志审计 Interceptor securityAuditInterceptor = invocation -> { System.out.println("[Security] 校验操作权限通过,用户合法"); Object result = invocation.proceed(); // 推进到目标方法 System.out.println("[Security] 审计日志记录完毕"); return result; }; // 4. 利用工厂组装代理 MiniProxyFactory factory = new MiniProxyFactory(); factory.setTarget(targetService); factory.addInterceptor(timeCostInterceptor); factory.addInterceptor(securityAuditInterceptor); PaymentService proxy = factory.getProxy(); // 5. 触发调用 proxy.pay("ORDER_20261014_99", 599.0); } }运行这段控制台代码,控制台打印出的输出完美验证了洋葱模型的进出栈顺序:
[TimeCost] 方法调用前置计时开始: pay [Security] 校验操作权限通过,用户合法 >>> 核心业务执行:正在扣除账户金额: 599.0, 订单号: ORDER_20261014_99 [Security] 审计日志记录完毕 [TimeCost] 方法调用结束,耗时: 1ms四、Spring 源码是如何在此基础上“扩写”的?
当你真正亲手写出了这个 Mini AOP,再去看 Spring AOP 的源码,你就会有一种“一览众山小”的豁然开朗感:
- 我们的
ReflectiveMethodInvocation.proceed(),在 Spring 中对应的是org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(),两者的变量名、递归推进逻辑几乎一模一样; - 我们的
MiniProxyFactory,在 Spring 中对应的是org.springframework.aop.framework.ProxyFactory; - Spring 只是在外面多包装了几层:
- 增加了 Pointcut 切点匹配(判断这个方法到底该不该进这个切面);
- 支持了 CGLIB 字节码生成,允许为没有接口的类生成代理子类;
- 结合 BeanPostProcessor 实现了在 Spring 容器启动时自动寻找
@Aspect注解并完成切面装配。
五、进阶思考:异常穿透与切点匹配器(Pointcut)扩展
如果我们想把这个 Mini AOP 进一步升级为准生产级框架,有两个关键设计细节必须重点考量:
1. 异常如何安全穿透拦截链?
在上面的Interceptor实现中,如果最内层的真实业务方法抛出了异常(比如数据库死锁或余额不足):
异常会直接顺着递归调用栈一路向上抛出。外层的拦截器如果使用try-finally(如我们的timeCostInterceptor),其finally块依然会被可靠触发执行,从而保证了统计耗时或释放锁逻辑的闭环;而如果拦截器捕获了异常并重新包装(如事务切面的回滚处理),则可以精准控制向调用端暴露的错误信息。
2. 如何实现注解式切点过滤?
在目前的 Mini AOP 中,拦截器是对接口中的所有方法一视同仁全量拦截的。
若要实现类似@Transactional的精准匹配,只需在MiniProxyFactory中引入一个Pointcut接口:
public interface Pointcut { boolean matches(Method method, Class<?> targetClass); }在每次进入ReflectiveMethodInvocation.proceed()时,先判断当前拦截器绑定的Pointcut.matches()是否返回 true。若不匹配,则直接递归调用proceed()跳过当前拦截器,立即执行下一个拦截器。这种“Advisor = Pointcut + Advice”的组合设计,正是 Spring AOP 最精髓的核心概念模型。
六、老鸭梨的总结
很多程序员总觉得大厂开源框架高深莫测,产生了一种盲目的敬畏感,甚至沦为“只知注解不知原理”的 API 搬砖工。
但真正的技术功力,在于穿透繁杂的代码外衣,直接洞察其背后的经典设计模式与核心骨架。花半小时亲自动手敲一遍 Mini AOP,你对动态代理和 Spring 底层的理解,绝对会比死记硬背十套面试八股文深刻十倍。在未来的架构演进中,无论遇到任何底层的拦截需求,你都能做到胸有成竹、手到擒来。