SpringBoot 学习笔记 Part10
1. 请求映射
从@RequestMapping标注处理什么请求,到控制器方法的方法体返回值,这个过程就叫做请求映射,如下:
@RequestMapping("hello")
public String hello(){
return "Hello SpringBoot 2 !";
}
2. 请求映射原理
2.1 DispatcherServlet
springboot 所有的请求过来都会经过 DispatcherServlet,因为springboot底层仍用的是 springmvc,所以 DispatcherServlet 是处理所有请求的开始。
前往底层,我们可以发现 DispatcherServlet 最终继承于原生Servlet—— HttpServlet,其中继承树为 DispatcherServlet -> FrameworkServlet -> HttpServletBean -> HttpServlet。
现在我们想知道在哪个派生类中重写了 doGet 和 doPost,前往底层源码可以发现,在 FrameworkServlet 中有如下代码:
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
/* 略 */
protected final void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.processRequest(request, response);
}
protected final void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.processRequest(request, response);
}
protected final void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.processRequest(request, response);
}
protected final void doDelete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.processRequest(request, response);
}
/* 略 */
}
在 FrameworkServlet 的处理请求方法中都调用了本类的 processRequest( ) 方法,这个方法经分析,有大量初始化和清理操作,其中最重要的是它调用的 doService( ) 方法。
protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
/* 略 */
this.doService(request, response);
/* 略 */
}
追踪该 doService( ) 方法,我们发现这也是一个本类的方法,是一个抽象方法。不难推测,这个抽象方法将在下一个派生类也就是 DispatcherServlet 中实现。
protected abstract void doService(HttpServletRequest request, HttpServletResponse response) throws Exception;
在 DispatcherServlet 的 doService 的实现方法中,有大量的处理,但我们最终把目光聚集到 doDispatcher 方法上。
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
/* 略 */
this.doDispatch(request, response);
/* 略 */
}
我们最终发现, doDispatcher 方法才是真正有功能的方法,它检查文件上传、确定当前请求的处理程序(找到当前请求使用哪个Controller方法处理) 等。
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
/* 略 */
processedRequest = this.checkMultipart(request);
mappedHandler = this.getHandler(processedRequest);
/* 略 */
}
总结:当有请求进来时,调用顺序为:HttpServlet.doGet() -> FrameworkServlet.processRequest().doService() -> DispatcherServlet.doService().doDispatch()
2.2 doDispatch方法
doDispatcher 方法才是真正有请求处理功能的方法,它检查文件上传、确定当前请求的处理程序(找到当前请求使用哪个Controller方法处理) 等。节选源码如下,逐步分析。
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
/* 略 */
processedRequest = this.checkMultipart(request); //检查是不是文件上传
multipartRequestParsed = processedRequest != request;
mappedHandler = this.getHandler(processedRequest); //找到当前请求使用哪个Controller方法处理
/* 其他功能,略 */
}
2.3 getHandler方法
从 doDispatch方法 进入 getHandler方法。
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null)
/* 略 */
}
HandlerMappings 译为处理器映射,即 /xxx 将被映射为 xxx,所有的处理器映射都被存在 handlerMappings 对象里。对第一行handlerMappings判空代码进行debug调试,可以发现存在5个handlerMapping。
其中下标为1的就是我们之前学习过的欢迎页,即springmvc在该对象里储存了欢迎页的映射规则。不难发现,下标为0的 RequestMappingHandlerMapping的前半段RequestMapping酷似我们之前学习过的注解 @RequestMapping,经过验证,这里就保存着我们所有被 @RequestMapping 标注过的 Controller 方法和handler的映射规则。其实项目一启动,@RequestMapping的映射规则就被springmvc解析并存储了。
继续 getHandler 方法的分析,下面是一个循环,遍历所有 @RequestMapping 映射规则意在找到对应的handler方法。通过debug,可以找到mappingRegistry对象,我之前编写的rest请求映射,都存在这里面。
总结:所有的请求映射都存在HandlerMappings中。
2.4 HandlerMappings
SpringBoot 中所有的请求映射都在 HandlerMappings 里。
在handlerMappings容器中我们可以发现,SpringBoot 自动配置了默认的 RequestMappingHandlerMapping、自动配置了欢迎页的 WelcomePageHandlerMapping(即使用 / 能访问到 index.html),以及之后学习的 BeanNameHandlerMapping、RouterFunctionMapping以及 SimpleUrlHandlerMapping。
SpringBoot的请求进来,会在getHandler方法里挨个尝试所有的 HandlerMapping 看其是否有对应的请求信息。若有则找到这个请求对应的 handler 并调用,若没有则去找下一个 HandlerMapping。
自定义HandlerMapping:
若在一些场景(如不同版本的方法调用:/api/v1/user、/api/v2/user)我们需要自定义一些映射处理,我们也可以自己给容器中放 自定义HandlerMapping。