news 2026/10/15 19:35:55

不用 Spring AOP,如何用 100 行 Java 代码手写一个带链式拦截的 mini AOP?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
不用 Spring AOP,如何用 100 行 Java 代码手写一个带链式拦截的 mini AOP?

在面试或者团队技术分享时,每当聊到 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 的核心契约设计

要实现链式拦截,我们只需要三个核心接口:

  1. MethodInvocation:方法调用上下文,负责驱动拦截链一步步向前推进;
  2. Interceptor:拦截器接口,定义切面逻辑,类似于 Spring 的MethodInterceptor;
  3. 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 的源码,你就会有一种“一览众山小”的豁然开朗感:

  1. 我们的ReflectiveMethodInvocation.proceed(),在 Spring 中对应的是org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(),两者的变量名、递归推进逻辑几乎一模一样;
  2. 我们的MiniProxyFactory,在 Spring 中对应的是org.springframework.aop.framework.ProxyFactory;
  3. 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 底层的理解,绝对会比死记硬背十套面试八股文深刻十倍。在未来的架构演进中,无论遇到任何底层的拦截需求,你都能做到胸有成竹、手到擒来。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/15 19:35:36

sqllibs靶场Less-1到Less-10:SQL注入闭合技巧与sqlmap实战

1. 从零理解 sqllibs 靶场的训练价值很多人第一次接触 SQL 注入&#xff0c;都是从 sqllibs 这个靶场开始的。它不像真实业务系统那样复杂&#xff0c;但每一关都精准地指向一个具体的注入场景——联合查询、报错注入、布尔盲注、时间盲注、堆叠注入、二次注入等等。我见过不少…

作者头像 李华
网站建设 2026/10/15 19:35:27

Facebook 推出自然语言算法控制工具

Facebook 在 10 月 13 日推出了名为 Your algorithm 的 AI 工具&#xff0c;用户可以用自己的话直接告诉平台想看什么内容&#xff0c;影响 Feed 和 Reels 的推荐结果。排名系统会实时适应用户输入。解决的问题 传统推荐算法依赖点赞、分享、停留时长这些行为信号推测用户兴趣&…

作者头像 李华
网站建设 2026/10/15 19:35:12

研究生开题报告撰写全指南 高效完成学术开题的实用技巧分享

外文文献“白嫖”与高效阅读全攻略 宝子们&#xff0c;是不是一提到下载外文文献就头大&#xff1f; 想找的文献要么全网搜不到&#xff0c;要么下载流程复杂到让人原地爆炸。好不容易在某个小网站找到了资源&#xff0c;又担心版权问题不敢点&#xff0c;更别说有些平台动辄几…

作者头像 李华
网站建设 2026/10/15 19:34:21

自命题863数据结构考研整理全攻略:从题型归类到手写代码实战

1. 为什么数据结构这门课值得花大力气整理考研计算机这条路上&#xff0c;数据结构几乎是所有院校绕不开的一门硬课&#xff0c;而自命题院校的863数据结构又和统考408里的数据结构部分有明显区别。统考覆盖面广但深度有限&#xff0c;自命题往往会在某些章节上挖得很深&#x…

作者头像 李华
网站建设 2026/10/15 19:32:14

用AI减少网点重复劳动:从识别问题到设计辅助工作流

网点日常运营中&#xff0c;很多流程环节会重复出现&#xff1a;同一份信息要在不同系统里重复录入&#xff0c;同一类资料要反复查找&#xff0c;同样的话术要一遍遍解释&#xff0c;任务交接时也常常衔接不清。这些环节占用时间&#xff0c;但大多不涉及交易决策本身。用AI分…

作者头像 李华