当前位置: 首页 > news >正文

剖析 Spring 中 @ResponseBody 原理与 Tomcat NIO 写事件(SelectionKey.OP_WRITE)的协作机制

在 Spring Web 开发领域,@ResponseBody 是实现 RESTful 接口的核心注解之一,它能够将方法的返回值直接转化为 HTTP 响应体。而 Tomcat 作为 Spring 常用的 Servlet 容器,在处理网络 IO 时采用了 NIO 模型,借助 SelectionKey.OP_WRITE 事件实现非阻塞式的写操作。下面将结合 Spring 5 和 Tomcat 源码,深入探究这两者的协同工作原理。

一、@ResponseBody 的核心处理逻辑(基于 Spring MVC 机制)

@ResponseBody 的作用是告知 Spring MVC,需将控制器方法的返回值通过消息转换器转化为 HTTP 响应体,而非解析为视图。其处理流程主要包含以下几个关键环节:

1. 处理器的识别与选取

当 Spring MVC 调度至目标控制器方法后,会调用 RequestMappingHandlerAdapter 的 handleReturnValue 方法来处理返回值。在此过程中,通过 selectHandler 方法从 returnValueHandlers 列表中筛选合适的处理器:

java

@Nullable
private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {boolean isAsyncValue = isAsyncReturnValue(value, returnType);for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {continue;}if (handler.supportsReturnType(returnType)) { // 检查是否支持 @ResponseBodyreturn handler;}}return null;
}

若方法或类上标注了 @ResponseBodysupportsReturnType 方法会返回 true,最终会选用 RequestResponseBodyMethodProcessor 作为处理器。

2. 消息转换与响应写入

RequestResponseBodyMethodProcessor 的 handleReturnValue 方法会调用 writeWithMessageConverters 方法,该方法的主要功能如下:

  • 依据请求头中的 Accept 字段,从注册的消息转换器(如 MappingJackson2HttpMessageConverter)中挑选出合适的转换器。
  • 将返回值(例如 Java 对象)转换为字节流,并写入 ServletServerHttpResponse 对应的 OutputStream 中。

java

@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {mavContainer.setRequestHandled(true); // 标记为直接处理响应体ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);writeWithMessageConverters(returnValue, returnType, null, outputMessage); // 执行转换与写入
}

值得注意的是,此时的写入操作仅仅是将数据写入到 Tomcat 的缓冲区中,并未真正通过网络发送数据,实际的网络发送由 Tomcat 的 NIO 模块负责。

二、Tomcat NIO 中的写事件(SelectionKey.OP_WRITE)处理流程

Tomcat 在处理 NIO 连接时,通过 Poller 线程管理 Selector,并借助 SelectionKey 监听 OP_READ 和 OP_WRITE 等事件。当 Spring 将数据写入缓冲区后,Tomcat 会依据缓冲区的状态决定是否注册 OP_WRITE 事件。

1. 写操作的初始尝试

在 Tomcat 的 NioChannel 实现类(如 SocketChannelImpl)的 write 方法中,会先尝试直接向套接字写入数据:

java

int cnt = socket.write(buf); // 尝试直接写入
if (cnt > 0) {time = System.currentTimeMillis(); // 重置超时时间continue; // 写入成功,无需注册事件
}

  • 若缓冲区有足够的空间,数据会直接写入套接字,此时无需注册 OP_WRITE 事件。
  • 若写入字节数为 0(表明套接字暂不可写),则需要通过 Poller 注册 OP_WRITE 事件,等待可写状态的通知。
2. 注册 OP_WRITE 事件与等待机制

当套接字暂不可写时,Tomcat 会执行以下操作:

java

poller.add(att, SelectionKey.OP_WRITE, reference); // 向 Poller 注册写事件
att.awaitWriteLatch(writeTimeout, TimeUnit.MILLISECONDS); // 等待可写通知或超时

  • Poller 的作用Poller 是 Tomcat NIO 中的事件轮询线程,负责将 SelectionKey 的注册 / 取消操作封装成任务,提交到 Selector 所在的线程执行,从而避免多线程竞争问题。
  • writeLatch 的作用:通过 CountDownLatch 实现线程间的通信。当 Selector 检测到套接字可写时,会触发 SelectionKey 的回调,调用 NioSocketWrapper 的 processWrite 方法,对 writeLatch 进行减计数,以唤醒等待线程。
3. 超时处理与事件取消

若在指定的 writeTimeout 时间内未收到可写通知,Tomcat 会抛出 SocketTimeoutException,并取消注册的 OP_WRITE 事件:

java

if (timedout) {poller.cancelKey(reference.key); // 取消事件注册throw new SocketTimeoutException();
}

通过这种超时机制,能够有效防止因套接字长时间不可写而导致的线程阻塞问题。

三、@ResponseBody 与 Tomcat NIO 的协作链路

下面以一个返回 JSON 数据的接口为例,梳理完整的处理流程:

  1. Spring MVC 处理返回值

    • 控制器方法返回 User 对象,@ResponseBody 触发 RequestResponseBodyMethodProcessor 对其进行处理。
    • MappingJackson2HttpMessageConverter 将 User 对象序列化为 JSON 字节流,并写入 ServletOutputStream,实际上是写入 Tomcat 的 ByteBuffer 缓冲区。
  2. Tomcat NIO 处理写操作

    • 当缓冲区已满,无法直接写入套接字时,Tomcat 会通过 Poller 向 Selector 注册 OP_WRITE 事件,并通过 writeLatch 阻塞当前线程。
    • Selector 轮询到 OP_WRITE 事件后,唤醒阻塞线程,再次尝试写入数据,直至所有数据都写入完毕或者超时。
  3. 关键类的协作关系

    • NioSocketWrapper:封装了套接字的状态,如 writeLatch 和 SelectionKey 的附件(attachment)。
    • KeyReference:作为 SelectionKey 的引用池,用于减少对象的创建和销毁开销。
    • Poller:负责协调 Selector 的事件注册和取消操作,确保线程安全。
四、总结

@ResponseBody 的核心原理是利用 Spring MVC 的消息转换机制,将方法返回值转化为字节流并写入响应缓冲区,而实际的网络发送则由 Tomcat 的 NIO 模块通过事件驱动的方式完成。当套接字暂不可写时,Tomcat 会通过注册 SelectionKey.OP_WRITE 事件实现非阻塞等待,这种机制充分发挥了 NIO 的优势,能够高效地处理高并发场景下的写操作。

通过深入理解这一流程,开发者可以更好地优化响应体的转换逻辑(如选择更高效的消息转换器),同时也能针对网络延迟、缓冲区设置等问题进行性能调优。

##源码

public int write(ByteBuffer buf, NioChannel socket, long writeTimeout)throws IOException {SelectionKey key = socket.getIOChannel().keyFor(socket.getSocketWrapper().getPoller().getSelector());if (key == null) {throw new IOException(sm.getString("nioBlockingSelector.keyNotRegistered"));}KeyReference reference = keyReferenceStack.pop();if (reference == null) {reference = new KeyReference();}NioSocketWrapper att = (NioSocketWrapper) key.attachment();int written = 0;boolean timedout = false;int keycount = 1; //assume we can writelong time = System.currentTimeMillis(); //start the timeout timertry {while (!timedout && buf.hasRemaining()) {if (keycount > 0) { //only write if we were registered for a writeint cnt = socket.write(buf); //write the dataif (cnt == -1) {throw new EOFException();}written += cnt;if (cnt > 0) {time = System.currentTimeMillis(); //reset our timeout timercontinue; //we successfully wrote, try again without a selector}}try {if (att.getWriteLatch() == null || att.getWriteLatch().getCount() == 0) {att.startWriteLatch(1);}poller.add(att, SelectionKey.OP_WRITE, reference);att.awaitWriteLatch(AbstractEndpoint.toTimeout(writeTimeout), TimeUnit.MILLISECONDS);} catch (InterruptedException ignore) {// Ignore}if (att.getWriteLatch() != null && att.getWriteLatch().getCount() > 0) {//we got interrupted, but we haven't received notification from the poller.keycount = 0;} else {//latch countdown has happenedkeycount = 1;att.resetWriteLatch();}if (writeTimeout > 0 && (keycount == 0)) {timedout = (System.currentTimeMillis() - time) >= writeTimeout;}}if (timedout) {throw new SocketTimeoutException();}} finally {poller.remove(att, SelectionKey.OP_WRITE);if (timedout && reference.key != null) {poller.cancelKey(reference.key);}reference.key = null;keyReferenceStack.push(reference);}return written;}@Overridepublic void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,ModelAndViewContainer mavContainer, NativeWebRequest webRequest)throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {mavContainer.setRequestHandled(true);ServletServerHttpRequest inputMessage = createInputMessage(webRequest);ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);// Try even with null return value. ResponseBodyAdvice could get involved.writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);}@Overridepublic void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);if (handler == null) {throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());}handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);}@Nullableprivate HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {boolean isAsyncValue = isAsyncReturnValue(value, returnType);for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {continue;}if (handler.supportsReturnType(returnType)) {return handler;}}return null;}@Overridepublic boolean supportsReturnType(MethodParameter returnType) {return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||returnType.hasMethodAnnotation(ResponseBody.class));}

相关文章:

  • [Windows] GDownload v1.0.0
  • 无损提速黑科技:YOLOv8+OREPA卷积优化方案解析(原理推导/代码实现/调参技巧三合一)
  • DAY 35 模型可视化与推理
  • 发电厂进阶,modbus TCP转ethernet ip网关如何赋能能源行业
  • 【c++】成员函数被声明为 `const` 时
  • 【一. Java基础:注释、变量与数据类型详解】
  • JavaScripts 中parseInt的作用
  • python训练营第33天
  • Windows 11 电源计划进阶——通过异类策略优化大小核CPU调度
  • OpenLayers 加载图层探查控件
  • 自动转换剪贴板中的字符串方便c#的$““符号输出
  • SQL语句的执行流程
  • 鼠标连点器 ,实现鼠标自动点击
  • 虚拟环境中的PyQt5 Pycharm设置参考
  • 使用YouDDNS-Docker为飞牛NAS配置YouDDNS动态域名解析
  • H3C-W2000-G2【透明代理模式】
  • 用wsl实现 kerberos 认证协议
  • 优启通添加自定义浏览器及EXLOAD使用技巧分享
  • vector中reserve导致的析构函数问题
  • 如何提高自己的实际操作技能以通过客运从业资格考试?
  • 做企业网站 需要注意的/互联网营销师教材
  • 动态倒计时网站模板/青岛seo用户体验
  • 网站毕业设计答辩问题/谷歌推广方案
  • 公司网站开发详细流程/网站设计模板
  • 苏州做网站专业的公司/cilimao磁力猫搜索引擎
  • 公司注册需要哪些资料/武汉网站开发公司seo