SpringMVC&SpringBoot请求映射原理

1.Servlet

1.1Servlet概述

servlet 是在 Web 服务器中运行的小型 Java 程序。Servlet 接收和响应来自 Web 客户端的请求,通常是通过 HTTP(超文本传输协议)。它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。

image-20230821020243234

Servlet就是一个接口,定义了Java类被浏览器访问到(tomcat识别)的规则。该接口主要用于接收请求参数,设置响应参数,在前后端中处理业务逻辑层的事务。

1.2Servlet体系结构

image-20230821015948612

  • 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

  1. SpringBoot底层还是使用的SpringMVC,当客户端浏览器发送请求时,都会到达DispatcherServlet,而DispatcherServlet继承于FrameworkServletFrameworkServlet继承于HttpServletBeanHttpServletBean继承于HttpServlet,所以本质上DispatcherServlet是一个Servlet。

DispatcherServlet

  1. 那么,在这些类中就要实现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);
}
  1. 可以看到四个方法都覆盖了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);
	}
}
  1. 实际的事件处理由抽象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;
  1. 发现其本身是抽象方法,查看其子类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

image-20230821023025659

断点到达mappedHandler.getHandler()所在行,这个方法决定哪个handler(controller中的方法)处理当前请求。然后放行,查看handler是什么。

选中mappedHandler = this.getHandler(processedRequest)的执行结果:可以查看已经映射到对应的方法

image-20230821023336554

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。

image-20230821024040559

  1. 其中第一个RequestMappingHandlerMapping中保存了@RequestMapping和handler的映射规则。当我们的应用一启动,SpringMVC自动扫描所有的Controller并解析注解,把注解信息保存在handlerMapping映射处理器中。
  2. 可以将这个HandlerMapping理解为一个Map,其中key为请求路径,value为handler的处理方法。
  3. 然后通过循环遍历着5个handlerMapping,看哪个可以处理当前请求。所以在getHandler()方法中,存在一个for循环,为了找到能处理对应请求的HandlerMapping。

进入循环中,当获取到RequestMappingHandlerMapping时,我们查看这个类中的属性值,可以看到所有标注了@RequestMapping注解的请求映射规则都已经存放在了这个RequestMappingHandlerMapping类中

image-20230821025529225

同时,已经获取到了相应的handler,也就是对应的Controller处理方法

2.4总结

所有的请求映射都保存在HandlerMapping映射处理器)中,在项目启动时,SpringMVC会自动扫描Controller并解析注解,将注解信息和处理方法保存在HandlerMapping映射处理器中。

SpringBoot为我们默认定义并配置了5个HandlerMapping,当一个请求进来时,系统会遍历这5个HandlerMapping,根据请求路径找到匹配的handler处理方法。

我们也可以将自定义的HandlerMapping放入容器中,使用自定义的映射处理器。


SpringMVC&SpringBoot请求映射原理
https://xhablog.online/2022/07/12/SpringMVC&SpringBoot请求映射原理/
作者
Xu huaiang
发布于
2022年7月12日
许可协议