本文打算介绍在 springboot 中,一个 http 请求从过来到响应结束的过程。主要通过介绍一些关键源码来说明。在分析源码之前我们可以先猜一下可能会有的过程。首先在我们的 springboot 应用中一般都会存在多个 controller handler 的方法,所以一个请求过来肯定是要先找到匹配该请求的方法。其次请求过来的参数需要处理成 controller 方法可以识别的参数格式,这样才能做进一步的处理。最后当 controller 的方法处理完之后,可能还要把返回值做一定的处理再返回给前端去。
所以本文重点介绍 url 映射 handler,参数解析,返回值处理三个过程。
首先,我们创建一个演示代码, springboot 版本我用的是 2.2.2.RELEASE,controller 代码如下:
package com.fc.study;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/fc-study")
public class HelloWorldController {
@GetMapping(value="/hello")
public String hello(@RequestParam("name") String name, @RequestParam("word") String word){
return "有人 "+name+" 说了一句话 "+word;
}
}
应用跑起来,浏览器访问结果如图:
众所周知 springboot 默认是 tomcat 容器 (SERVLET web类型)。我们要分析一个请求过来所经历的过程,自然要从前端总控制器(DispatchServlet)开始说起。按照 Servlet 规范,所有请求都会被tomcat容器交到 dispatchServlet 的 doService 方法中去处理。跟到这个方法中去,我们发现其中设置了变量进 request 对象,然后执行了 doDispatch 方法,这个方法才是真正实现请求处理的核心。
/**
* Exposes the DispatcherServlet-specific request attributes and delegates to {@link #doDispatch}
* for the actual dispatching.
*/
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
/* 为了博客篇幅我们省略了很多 request.setAttribute 的代码 */
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
try {
doDispatch(request, response);
}
finally {
}
}
以下是 doDispatch 方法,为篇幅考虑保留了关键代码, 我们看到首先调用 getHandler 找到 url 匹配的 handler 方法(示例代码中的 hello 方法)。然后调用 ha.handle() 来获得处理结果。
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
/* 后续处理 */
}
进入 getHandler 方法,我们看到是通过 HandlerMapping 接口对象的集合对象来操作的。HandlerMapping 接口要求实现类实现从请求到处理对象的映射的方法。有兴趣的同学可以断点看一下这个对象包含的集中实现,以 RequestMappingHandlerMapping 实现为例,它底层注册了一个 url -> handler方法的 map,每当请求过来,就会根据请求的url 去 map 中匹配,匹配到对应的handler 方法。
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
匹配到了 handler 方法之后,就到了 handler 方法的调用。通过断点,一步步的进到这个 handle 方法中去,我们发现最终的实现是在 ServletInvocableHandlerMethod 中实现的,它和父类分别实现了对返回值和参数的处理。
/* InvocableHandlerMethod 实现*/
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {
/* 1.参数解析 */
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
if (logger.isTraceEnabled()) {
logger.trace("Arguments: " + Arrays.toString(args));
}
return doInvoke(args);
}
/* ServletInvocableHandlerMethod 实现 */
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception {
/* 2.调用获得返回值 */
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
/* 3.处理返回值 */
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
以参数解析为器例子,springboot 实现了一个 Composite 对象,其中有一个 List 和一个 Map 来分别存储所有的扫描到的参数解析器以及曾经匹配过的解析器缓存。外部调用 resolveArgument 方法来进行解析,这个方法首先会调用 composite 的私有方法 getArgumentResolver 来获取参数解析器,然后再用参数解析器进行解析。获取解析器的过程首先是从缓存Map中去取,如果没有取到(也就是之前该种参数解析器被缓存在map中),则会去循环注册的所有解析器的 List,找出第一个可以解析当前请求参数的解析器,进行参数解析,并把它缓存。
public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver {
private final List<HandlerMethodArgumentResolver> argumentResolvers = new LinkedList<>();
private final Map<MethodParameter, HandlerMethodArgumentResolver> argumentResolverCache =
new ConcurrentHashMap<>(256);
@Override
@Nullable
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
if (resolver == null) {
throw new IllegalArgumentException("Unsupported parameter type [" +
parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
}
return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}
/**
* Find a registered {@link HandlerMethodArgumentResolver} that supports
* the given method parameter.
*/
@Nullable
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
if (result == null) {
for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
if (resolver.supportsParameter(parameter)) {
result = resolver;
this.argumentResolverCache.put(parameter, result);
break;
}
}
}
return result;
}
}
再看返回值处理,它主要是把 handler 方法获得的返回值,在model对象上做一些set操作。步骤大体也是类似的,实现了一个 **Composite 对象。在此不再赘述。最后,用一个表格来总结。
步骤 | 接口 | 实现方式 |
路径映射 | HandlerMapping | 在 DispatchServlet 中,springboot 注册了一个 HandlerMapping 列表。请求过来时,会循环该列表,来解析url获取handler方法,获取到之后即跳出循环。 |
参数解析 | HandlerMethodArgumentResolver | 在 InvocableHandlerMethod 中,springboot 注入了一个 **Composite 对象,该对象中有多个解析器放在列表中,解析参数时同样是循环列表,来找出第一个可以解析的解析器进行解析。 |
返回处理 | HandlerMethodReturnValueHanler | 在 ServletInvocableHandlerMethod 中,springboot 注入了一个 **Composite 对象,该对象中有多个返回值处理器在列表中,处理返回值时,首先同样是循环列表,找到处理器,再进行返回值处理。 |