SpringMVC&SpringBoot请求映射原理
1.Servlet
1.1Servlet概述
servlet 是在 Web 服务器中运行的小型 Java 程序。Servlet 接收和响应来自 Web 客户端的请求,通常是通过 HTTP(超文本传输协议)。它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。
Servlet就是一个接口,定义了Java类被浏览器访问到(tomcat识别)的规则。该接口主要用于接收请求参数,设置响应参数,在前后端中处理业务逻辑层的事务。
1.2Servlet体系结构
GenericServlet
:将Servlet接口中其他的方法做了默认空实现,只将service()方法作为抽象HttpServlet
:对http协议的一种封装,简化操作
1.3HttpServlet概述
HttpServlet 是一个抽象类,它属于 javax.servlet.http.HttpServlet
包。要创建一个 servlet,类必须继承 HttpServlet 类并重写至少一个方法(doGet、doPost、doDelete、doPut)。如果 servlet 支持 HTTP GET 请求,则为 doGet;对于 HTTP POST 请求,则为 doPost;对于 HTTP PUT 请求,则为 doPut。
2.SpringMVC&SpringBoot请求映射原理
当我们每次发送请求时,系统是如何找到对应的方法来处理请求的呢?为了解决这个问题,我们查看SpringMVC的底层源代码
2.1DispatcherServlet
- SpringBoot底层还是使用的SpringMVC,当客户端浏览器发送请求时,都会到达
DispatcherServlet
,而DispatcherServlet
继承于FrameworkServlet
,FrameworkServlet
继承于HttpServletBean
,HttpServletBean
继承于HttpServlet
,所以本质上DispatcherServlet
是一个Servlet。
- 那么,在这些类中就要实现
doGet()
或者doPost()
方法。我们看到,在HttpServletBean
这个类中并没有实现doGet后者doPost方法,那么我们查看FrameworkServlet
类代码。
/**
* 将 GET 请求委托给 processRequest/doService。
* <p>Will also be invoked by HttpServlet's default implementation of {@code doHead},
* with a {@code NoBodyResponse} that just captures the content length.
* @see #doService
* @see #doHead
*/
@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
/**
* Delegate POST requests to {@link #processRequest}.
* @see #doService
*/
@Override
protected final void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
/**
* Delegate PUT requests to {@link #processRequest}.
* @see #doService
*/
@Override
protected final void doPut(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
/**
* Delegate DELETE requests to {@link #processRequest}.
* @see #doService
*/
@Override
protected final void doDelete(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
- 可以看到四个方法都覆盖了
HttpServlet
中对应的方法,且实现都是processRequest()
方法。
/**
* 处理此请求,无论结果如何,都会发布事件。
* 实际的事件处理由抽象 doService 模板方法执行。
*/
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
LocaleContext localeContext = buildLocaleContext(request);
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
initContextHolders(request, localeContext, requestAttributes);
try {
//执行doService方法
doService(request, response);
}
catch (ServletException | IOException ex) {
failureCause = ex;
throw ex;
}
catch (Throwable ex) {
failureCause = ex;
throw new NestedServletException("Request processing failed", ex);
}
finally {
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
logResult(request, response, failureCause, asyncManager);
publishRequestHandledEvent(request, response, startTime, failureCause);
}
}
- 实际的事件处理由抽象
doService
模板方法执行。
- 子类必须实现此方法来完成请求处理的工作,接收 GET、POST、PUT 和 DELETE 的集中回调。
/**
* Subclasses must implement this method to do the work of request handling,
* receiving a centralized callback for GET, POST, PUT and DELETE.
* <p>The contract is essentially the same as that for the commonly overridden
* {@code doGet} or {@code doPost} methods of HttpServlet.
* <p>This class intercepts calls to ensure that exception handling and
* event publication takes place.
* @param request current HTTP request
* @param response current HTTP response
* @throws Exception in case of any kind of processing failure
* @see javax.servlet.http.HttpServlet#doGet
* @see javax.servlet.http.HttpServlet#doPost
*/
protected abstract void doService(HttpServletRequest request, HttpServletResponse response)
throws Exception;
- 发现其本身是抽象方法,查看其子类
DispatcherServlet
当中的定义情况。
公开DispatcherServlet特定的请求属性,并委托给
doDispatch
进行实际调度。即该方法内的核心是调用了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 {
logRequest(request);
// Keep a snapshot of the request attributes in case of an include,
// to be able to restore the original attributes after the include.
Map<String, Object> attributesSnapshot = null;
if (WebUtils.isIncludeRequest(request)) {
attributesSnapshot = new HashMap<>();
Enumeration<?> attrNames = request.getAttributeNames();
while (attrNames.hasMoreElements()) {
String attrName = (String) attrNames.nextElement();
if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
attributesSnapshot.put(attrName, request.getAttribute(attrName));
}
}
}
// Make framework objects available to handlers and view objects.
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
if (this.flashMapManager != null) {
FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
if (inputFlashMap != null) {
request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
}
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
}
RequestPath previousRequestPath = null;
if (this.parseRequestPath) {
previousRequestPath = (RequestPath) request.getAttribute(ServletRequestPathUtils.PATH_ATTRIBUTE);
ServletRequestPathUtils.parseAndCache(request);
}
try {
//公开DispatcherServlet特定的请求属性,并委托给`doDispatch`进行实际调度。
doDispatch(request, response);
}
finally {
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Restore the original attribute snapshot, in case of an include.
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
if (this.parseRequestPath) {
ServletRequestPathUtils.setParsedRequestPath(previousRequestPath, request);
}
}
}
2.2doDispatch()方法
源码如下:
/**
* Process the actual dispatching to the handler.
* <p>The handler will be obtained by applying the servlet's HandlerMappings in order.
* The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
* to find the first that supports the handler class.
* <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
* themselves to decide which methods are acceptable.
* @param request current HTTP request
* @param response current HTTP response
* @throws Exception in case of any kind of processing failure
*/
@SuppressWarnings("deprecation")
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;
}
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = HttpMethod.GET.matches(method);
if (isGet || HttpMethod.HEAD.matches(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
客户端发送Http请求,debug测试:
可以看到请求的路径就是为/reflect
断点到达mappedHandler.getHandler()
所在行,这个方法决定哪个handler(controller中的方法)处理当前请求。然后放行,查看handler是什么。
选中mappedHandler = this.getHandler(processedRequest)
的执行结果:可以查看已经映射到对应的方法
2.3HandlerMapping
进入getHandler()
方法
/**
* Return the HandlerExecutionChain for this request.返回此请求的处理程序执行链。
* <p>Tries all handler mappings in order.按顺序尝试所有处理程序映射。
* @param request current HTTP request
* @return the HandlerExecutionChain, or {@code null} if no handler could be found
*/
@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;
}
在方法的首行,获取了handlerMappings
处理器映射,SpringMVC根据处理器映射里面的映射规则找到对应的处理方法。默认有5个HandlerMapping。
- 其中第一个
RequestMappingHandlerMapping
中保存了@RequestMapping和handler
的映射规则。当我们的应用一启动,SpringMVC自动扫描所有的Controller
并解析注解,把注解信息保存在handlerMapping
映射处理器中。- 可以将这个HandlerMapping理解为一个Map,其中key为请求路径,value为handler的处理方法。
- 然后通过循环遍历着5个handlerMapping,看哪个可以处理当前请求。所以在
getHandler()
方法中,存在一个for循环,为了找到能处理对应请求的HandlerMapping。
进入循环中,当获取到RequestMappingHandlerMapping
时,我们查看这个类中的属性值,可以看到所有标注了@RequestMapping
注解的请求映射规则都已经存放在了这个RequestMappingHandlerMapping
类中
同时,已经获取到了相应的handler,也就是对应的Controller处理方法
2.4总结
所有的请求映射都保存在HandlerMapping
(映射处理器)中,在项目启动时,SpringMVC会自动扫描Controller并解析注解,将注解信息和处理方法保存在HandlerMapping映射处理器中。
SpringBoot为我们默认定义并配置了5个HandlerMapping,当一个请求进来时,系统会遍历这5个HandlerMapping,根据请求路径找到匹配的handler处理方法。
我们也可以将自定义的HandlerMapping放入容器中,使用自定义的映射处理器。