SpringBoot2:请求处理原理分析-请求Path与接口的映射关系(HandlerMapping)
一、知识回顾
以前,我们开发JavaWeb
时,如果要写个接口,都是要配置Servlet
的。
首先,什么是Servlet
了?可以简单的理解,Servlet
是指Java
语言实现的一个接口
简单案例如下:
pom
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
Java
代码
主要是实现doGet
和doPost
接口,然后,重写自己的业务逻辑即可。
这里模拟一个登陆逻辑。
package com.atguigu.boot.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
public class UserServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=UTF-8");
req.setCharacterEncoding("utf-8");
resp.setCharacterEncoding("utf-8");
String username = req.getParameter("username");
String pwd = req.getParameter("pwd");
System.out.println("用户名:" + username + ":密码:" + pwd);
if("admin".equals(username) && "admin".equals(pwd)){
PrintWriter writer = resp.getWriter(); // 输出流
writer.print("用户名:" + username);
writer.print("密码:" + pwd);
writer.close();
} else {
PrintWriter writer = resp.getWriter(); // 输出流
writer.print("用户名或密码不正确!!" );
}
}
}
web.xml
配置
主要配置servlet
和servlet-mapping
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<servlet>
<servlet-name>userServlet</servlet-name>
<servlet-class>com.atguigu.boot.servlet.UserServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>userServlet</servlet-name>
<url-pattern>/userLogin</url-pattern>
</servlet-mapping>
</web-app >
以上,就是一个简单的servlet
接口实现。
那么,在springboot
中,我们经常听说的是controller
接口。
实际上,底层对应的依然是servlet
接口。
所以,我们要探究springboot
的接口原理,就要从servlet
入手。
二、源码解读
通过IDEA
全局搜索HttpServlet
,并查看其继承关系图
我们会看到很多的子类,这里,我们看到一个熟悉的类DispatcherServlet
我们在之前使用springmvc
的时候知道
web
整合springmvc
配置如下
<!-- springMVC核心配置 -->
<servlet>
<servlet-name>spring</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:conf/spring-mvc.xml</param-value>
</init-param>
<load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>spring</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
所以,我们从这个类着手,开始探究springboot
接口原理。
1、梳理继承关系,找到主要执行方法
我们知道,自定义Servlet
接口,要重写doGet
和doPost
方法
从图中我们可以看出,最终在DispatcherServlet
的doService
中调用doDispatch
处理请求。
所以,我们直接定位到doDispatch
方法内。
org.springframework.web.servlet.DispatcherServlet#doDispatch
2、映射原理
org.springframework.web.servlet.DispatcherServlet#getHandler
这里,我们找到了主要研究目标对象,handlerMappings
变量。
可以看到,有5种类型的handlerMapping
在对象的mappingLookup
属性里面,保存了请求Path
与接口方法的映射关系。
getHandler
方法内,循环遍历所有的HandlerMapping
,查找请求的处理方法,最后,返回处理结果,完成一次请求。
三、逻辑梳理
简单的理解就是,springboot
中的接口,本质是servlet
接口。
springboot
的servlet
就是springmvc
中的DispatcherServlet
。
请求path
和接口的映射关系,保存在HandlerMapping
对象中。
springboot
中默认配置了5类HandlerMapping
所有请求,都是经过DispatcherServlet
来处理,从所有的HandlerMapping
中遍历查找对应的处理接口,最终,返回处理结果。
四、补充
这5类HandlerMapping
,我们最需要关心的是RequestMappingHandlerMapping
这个就是处理我们controller
接口请求的HandlerMapping
而RequestMappingHandlerMapping
是通过EnableWebMvcConfiguration
配置类,注册到IOC
容器中的。
所以,我们如果想自定义一个HandlerMapping
,也可以通过配置类,注册一个HandlerMapping
到IOC
容器中即可。
另外,我们如果想查看工程中所有的HandlerMapping
映射关系
可以做如下配置:
logging:
level:
org.springframework.web: trace
这样,工程启动时,会打印出所有的映射关系