读 Netty 与 MVC 的集成代码时,可以先抓住三个位置:请求从哪里进入,谁来调用处理器,处理结果在哪里写回连接。
本文使用 TODAY Infrastructure 的同步 HTTP 请求作为例子。我是项目维护者,也向 Spring Framework、MyBatis 和 Halo 贡献过代码,其中一些问题是在维护本项目时发现的。项目部分实现来自 Spring Framework / Spring
Boot
的移植与适配,也有自己的实现。下文只介绍这套框架的分发骨架,注解匹配、参数解析和返回值转换的全部细节没有展开。
最小控制器与调用链
示例中/index返回字符串Hello。以下仅为控制器相关代码,不是独立启动项目:
importinfra.web.annotation.GET;importinfra.web.annotation.RestController;@RestControllerclassDemoController{@GET("/index")publicStringindex(){return"Hello";}}请求处理的主干如下:
HttpRequest → HttpTrafficHandler.channelRead → 创建请求上下文 → HttpRequestExecutor.execute → HttpContext.run → DispatcherHandler.handleRequest → 处理器查找、执行与返回值处理 → 同步请求完成,响应写回过滤器、异常和异步请求还会走其他分支。先连起主干,再读各个策略实现更容易定位。
请求接入:HttpTrafficHandler
channelRead收到的是 Netty 已解码的HttpRequest。它创建请求上下文,存入 Channel 属性,然后把上下文任务交给执行器:
executor.execute(httpContext,httpContext);执行器的类型是HttpRequestExecutor。上下文同时实现Runnable,因此在这里既是请求信息,也是待执行任务。
HttpRequest 与 HttpContent
不要把收到请求头理解为请求体完整到达。后续HttpContent会交给 Channel 上保存的上下文,由onDataReceived继续处理。
这意味着请求体读取要和网络数据到达衔接,不能只包装一下请求头就结束适配。
执行器与线程模型
委托执行不意味着必然使用线程池或虚拟线程,具体要看实现和配置。
上下文任务的run方法检查解码错误等情况;正常请求会进入dispatcherHandler.handleRequest(this),并在finally清理上下文持有者。
MVC 分发:查找、调用与返回值处理
DispatcherHandler组织请求处理流程,其中有三个主要职责:
| 工作 | 对应策略 |
|---|---|
| 查找请求处理器 | HandlerMapping |
| 执行处理器 | 直接调用 HttpRequestHandler,或由 HandlerAdapter 执行 |
| 处理返回值 | ReturnValueHandler |
lookupHandler委托HandlerMapping.getHandler(context)查找处理器。找到后,若它实现HttpRequestHandler,直接调用handleRequest;否则通过HandlerAdapter执行。
返回"Hello"后,分发器还要选择返回值处理策略。此时取得的是 Java 对象,不等于响应已经发送给浏览器。执行失败的异常也有相应处理路径。
响应写回:NettyHttpContext
MVC 面向统一的infra.web.HttpContext。Netty 包内还有同名实现infra.web.server.netty.HttpContext,它继承NettyHttpContext并承担上下文任务入口。
统一接口减少了上层对 Netty 具体对象的直接依赖,适配层仍负责请求体、响应提交、断连及资源清理。
普通同步请求完成后,Netty 实现负责写回响应。NettyHttpContext.flush写出响应头和正文或文件,请求完成时还要处理结束标记及连接是否保持开启。
因此读源码时,可以把调用控制器、处理返回值、写回连接分开看,再沿请求完成路径把它们连起来。
运行示例
先根据仓库快速开始准备 JDK 与 Gradle 运行环境:
gitclone https://github.com/TAKETODAY/today-infrastructure.gitcdtoday-infrastructure/infra-samples ./gradlew infraRun另开终端检查:
curl-ihttp://localhost:8080/index预期状态 HTTP 200,正文Hello。如需调整端口:
./gradlew infraRun--args='--server.port=8081'curl-ihttp://localhost:8081/index上面两条分别在服务终端和另一个请求终端执行,服务不会自然退出。
实测环境与范围
| 日期 | 示例源码提交 | 构建与运行方式 | 结果 |
|---|---|---|---|
| 2026-10-11 | 88f5240e85859d64eb3c9543a528afb41b53519f | macOS ARM64,JDK 25.0.1 / Gradle 9.8.0,隔离缓存与 Maven Local,执行 infraRun | 默认端口及 8081 返回 HTTP 200 / Hello |
| 2026-10-13 | a880d8b5ba6bd73e65333fd73ec0679bd9b130df | macOS ARM64,JDK 25.0.3 / Gradle 9.8.0 构建发行包,JDK 17.0.16 运行同一产物 | /index 返回 HTTP 200 / Hello |
JDK 17 的结果只覆盖默认示例路径,没有用它执行 Gradle,也没有验证所有可选功能。示例解析的是 Maven 仓库中的5.0-Draft.8-SNAPSHOT,快照可能变化。
本文源码分析固定在7045f9c1fd1b4fdda909674807fa6fd433b2bed5;它与实测示例提交不同。克隆命令取得当前默认分支,运行时应结合当前快速开始说明,历史记录不等于当前提交的全功能验证。
源码位置
- 示例控制器
- HttpTrafficHandler.channelRead
- HttpContext.run
- DispatcherHandler 执行与完成
- 返回值与异常处理
- NettyHttpContext.flush
同作者博客文章:https://taketoday.cn/articles/today-infrastructure-netty-to-mvc