写点什么

Spring 源码解析系列:Spring Web 请求初探

用户头像
关注
发布于: 5 小时前

简介

这几年工作中大部分时间都与 SpringWeb 打交道,前几年源码阅读能力和方法论不行,在疫情期间学习了一波,有了源码阅读与分析能力,目前也有了一些闲暇时间,以目前自己的水平去写写 Spring 相关的源码解析


本篇文章就先简单热个身,看看一个请求是如何处理到达我们平时写的 Controllers 里面的

准备工作

准备工作较为简单,我们使用 spring initializr,搭建一个初始工程,写一个简单的 HelloWorld


import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;
@RestControllerpublic class HelloWorld {
@GetMapping("/") public String helloWorld() { return "Hello world"; }}
复制代码


推荐 IDEA 上一个比较好用的插件:RestfulTool 和 RestfulToolKit


启动项目后,会读取所有的请求列表,简单快捷的发起一个请求:



这样,本章的初步环境就搭建好了,下面开始源码分析:

源码解析

我们直接在新增的 HelloWorld 类的:


return "Hello world"; 
复制代码


这行打断点,直接发起请求,可以看到下面的调用栈:



我们从下网上,大体的点击进去,看看大意


带着问题去看:


  • 从那接收的请求:如何写过相关网络编程的,那就会想到起码有一个 Netty 之类的服务,监听在指定端口,接收请求

  • 接收到的请求如何找到我们写的页面代码(HelloWorld):比如如何将请求路径和处理函数进行对应


一个简单的本地探索的请求如下:


找到大致的监听入口

我们从下往上看是,发现有一个类:NioEndpoint.java,虽然看不太懂更多的细节,但知道有这么一个东西即可

找到重要的 Request 和 Response 处理函数

我们继续来到一个重要的类:Http11Processor.java,其中有添加 Filter 相关的代码,还是明显的 Request 和 Response 的处理


这里我们留下两个疑问,目前我们先把请求路径梳理下,这些细节日后我们再回过头来啃它


  • 这个类中设置的 Filter 如何使用

  • Request 和 Response 是如何转化得到的


public class Http11Processor extends AbstractProcessor {    @Override    public SocketState service(SocketWrapperBase<?> socketWrapper)        throws IOException {        RequestInfo rp = request.getRequestProcessor();        rp.setStage(org.apache.coyote.Constants.STAGE_PARSE);
// Setting up the I/O setSocketWrapper(socketWrapper);
// Flags keepAlive = true; openSocket = false; readComplete = true; boolean keptAlive = false; SendfileState sendfileState = SendfileState.DONE;
while (!getErrorState().isError() && keepAlive && !isAsync() && upgradeToken == null && sendfileState == SendfileState.DONE && !protocol.isPaused()) {
// Parsing the request header try { ...... }
// Has an upgrade been requested? if (isConnectionToken(request.getMimeHeaders(), "upgrade")) { ...... } ......
// Process the request in the adapter if (getErrorState().isIoAllowed()) { try { rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE); getAdapter().service(request, response); // Handle when the response was committed before a serious // error occurred. Throwing a ServletException should both // set the status to 500 and set the errorException. // If we fail here, then the response is likely already // committed, so we can't try and set headers. if(keepAlive && !getErrorState().isError() && !isAsync() && statusDropsConnection(response.getStatus())) { setErrorState(ErrorState.CLOSE_CLEAN, null); } } catch (InterruptedIOException e) { setErrorState(ErrorState.CLOSE_CONNECTION_NOW, e); } catch (HeadersTooLargeException e) { log.error(sm.getString("http11processor.request.process"), e); // The response should not have been committed but check it // anyway to be safe if (response.isCommitted()) { setErrorState(ErrorState.CLOSE_NOW, e); } else { response.reset(); response.setStatus(500); setErrorState(ErrorState.CLOSE_CLEAN, e); response.setHeader("Connection", "close"); // TODO: Remove } } catch (Throwable t) { ExceptionUtils.handleThrowable(t); log.error(sm.getString("http11processor.request.process"), t); // 500 - Internal Server Error response.setStatus(500); setErrorState(ErrorState.CLOSE_CLEAN, t); getAdapter().log(request, response, 0); } } } ...... }}
复制代码

经过了一系列的 Valva 类处理

继续往上查看,我们看到了很多的 Value 相关的处理,类型一种责任链的处理方式,但目前还不知道其具体作用,但没关系,我们继续往下看

经过了一系列的 Filter 处理

经过上面那些 Valva 类的处理后,来到了我们熟悉的 Filter 处理,具体细节我们先不深究,继续往下,留下疑问待日后处理


  • Filter 如何初始化的

  • 如何针对指定的请求路径进行处理

  • 自定义添加的 Filter 会插入其中吗?如何插入?

请求路径到具体函数处理

继续走,我们来到属性的类:DispatcherServlet.java,平时看文章之类的,这个类的出现率还是挺高的


通过断点调试,我们发现在这里直接得到了我们请求路径对应的请求方法,如下代码中标注的:


public class DispatcherServlet {    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {  HttpServletRequest processedRequest = request;  HandlerExecutionChain mappedHandler = null;  boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try { ModelAndView mv = null; Exception dispatchException = null;
try { processedRequest = checkMultipart(request); multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request. // 得到请求的处理方法 mappedHandler = getHandler(processedRequest); if (mappedHandler == null) { noHandlerFound(processedRequest, response); return; }
if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; }
// Actually invoke the handler. // 这行通过断点查看:mappedHandler就是我们请求的处理方法 mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) { return; }
applyDefaultViewName(processedRequest, mv); mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception ex) { ........ } }}
复制代码


我们继续往下,来到了一个类:InvocableHandlerMethod.java,其中的:method(getBean(), args),相比很熟悉了,经典的反射调用


public class InvocableHandlerMethod {  protected Object doInvoke(Object... args) throws Exception {    Method method = getBridgedMethod();    ReflectionUtils.makeAccessible(method);    try {      if (KotlinDetector.isSuspendingFunction(method)) {        return CoroutinesUtils.invokeSuspendingFunction(method, getBean(), args);      }      return method.invoke(getBean(), args);    }    catch (IllegalArgumentException ex) {      ......    }  }}
复制代码


在这里我们也留下疑问:


  • 如何得到请求路径对应的处理方法?

  • 如何解析获取处理方法的参数的?

总结

经过上面的简单分析,我们大致得到了一个请求的处理路径:


  • NioEndpoint.java : 服务监听

  • Http11Processor.java : 请求与响应处理入口

  • 一系列的 Valva 类处理

  • 一系列的 Filter 处理

  • DispatcherServlet.java : 请求路径到具体处理方法

  • InvocableHandlerMethod.java : 反射调用处理


当前我们拿到一个比较大的地图,但很多地方还被迷雾笼罩,接下来我们慢慢去除迷雾,探索其奥秘

发布于: 5 小时前阅读数: 2
用户头像

关注

还未添加个人签名 2018.09.09 加入

代码是门手艺活,也是门艺术活

评论

发布
暂无评论
Spring源码解析系列:Spring Web 请求初探