SpringMVC源码解析——DispatcherServlet的逻辑处理

DispatcherServlet类相关的结构图如下:

其中jakarta.servlet.http.HttpServlet的父类是jakarta.servlet.GenericServlet,实现接口jakarta.servlet.Servlet。我们先看一下jakarta.servlet.Servlet接口的源码如下:

/*** 定义所有servlet必须实现的方法。** servlet是一个小型的Java程序,它在Web服务器中运行。servlet从Web客户端接收和响应请求,通常使用超文本传输协议(HTTP)。** 要实现此接口,可以编写一个通用servlet,继承`jakarta.servlet.GenericServlet`,或编写一个HTTP servlet,继承`jakarta.servlet.http.HttpServlet`。** 此接口定义了初始化servlet、服务请求和将servlet从服务器中删除的方法。这些被称为生命周期方法,并按照以下序列进行调用:* <ol>* <li>构造servlet,然后使用`init`方法进行初始化。* <li>任何来自客户端的对`service`方法的调用将被处理。* <li>取消服务servlet,然后使用`destroy`方法将其从服务器中删除,然后进行垃圾回收和 finalize。* </ol>** <p>* 除了生命周期方法外,此接口还提供了`getServletConfig`方法,servlet可以使用该方法获取任何启动信息,并提供了`getServletInfo`方法,servlet可以返回有关自身的基本信息,例如作者、版本和版权。**/
public interface Servlet {/*** 由servlet容器调用,表示将servlet放入服务中的操作。** <p>* servlet容器在实例化servlet后,仅在成功初始化后,才会调用此方法。在servlet接收任何请求之前,servlet容器必须确保`init`方法完成。容器将确保`init`方法在任何随后调用`service`方法的线程中可见(按照JSR-133的规定,存在一个`init`与`service`之间的'happens before'关系)。** <p>* servlet容器无法将servlet放入服务中,如果:* <ol>* <li>抛出`ServletException`* <li>超时时间内未返回* </ol>*** @param config 一个`ServletConfig`对象,包含servlet的配置和初始化参数** @exception ServletException 如果干扰了servlet的正常操作而引发的异常** @see UnavailableException* @see #getServletConfig**/public void init(ServletConfig config) throws ServletException;/**** 返回一个`ServletConfig`对象,其中包含此servlet的初始化和启动参数。在`init`方法中返回的`ServletConfig`对象将被此方法返回。** <p>* 此接口的实现负责存储`ServletConfig`对象,以便此方法可以返回它。`GenericServlet`类已实现此接口。** @return 初始化此servlet的`ServletConfig`对象** @see #init**/public ServletConfig getServletConfig();/*** 由servlet容器调用,允许servlet响应请求。** <p>* 此方法仅在servlet的`init()`方法成功完成之后才被调用。** <p>* 发生错误时必须设置响应状态码。** * <p>* `Servlet`通常在一个可以同时处理多个请求的多线程servlet容器中运行。开发人员必须注意同步访问任何共享资源,例如文件、网络连接以及servlet的类和实例变量。** @param req 传递客户端请求的`ServletRequest`对象** @param res 传递servlet响应的`ServletResponse`对象** @exception ServletException 如果干扰了servlet的正常操作而引发的异常** @exception IOException 如果发生输入或输出异常**/public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;/*** 返回有关servlet的信息,例如作者、版本和版权。** <p>* 该方法返回的字符串应为纯文本,而不是任何标记(例如HTML、XML等)。** @return servlet信息的字符串**/public String getServletInfo();/**** 由servlet容器调用,表示将servlet从服务中取出。此方法仅在所有线程都退出`service`方法或超时之后才被调用。在servlet容器调用此方法之后,将不再调用`service`方法。** <p>* 这个方法给servlet一个清理任何资源(例如内存、文件句柄、线程)的机会,并确保任何持久状态与servlet当前在内存中的状态保持同步。**/public void destroy();
}

其实,关键的三个函数init、service和destroy分别用于控制Servlet的初始化、运行和销毁。在SpringMVC源码解析 —— DispatcherServlet初始化

中已经介绍了Servlet的初始化过程,本次主要是介绍jakarta.servlet.Servlet接口是如何处理servlet从Web客户端接收和响应请求。

接下来我们可以看一下jakarta.servlet.GenericServlet类,该类是一个通用的、与协议无关的 servlet,作为Servlet和ServletConfig的抽象实现类,该类并没有进行特殊的逻辑处理,只是实现了ServletConfig接口的功能。而对Servlet接口的函数只提供了一个空的实现,具体逻辑需要在jakarta.servlet.GenericServlet的子类中完成。jakarta.servlet.GenericServlet类的源码如下:

/*** 定义了一个通用的、协议无关的servlet。要编写一个HTTP servlet来在Web上使用,请扩展* {@link jakarta.servlet.http.HttpServlet}而不是本类。** <p>* <code>GenericServlet</code> 实现了 <code>Servlet</code> 和 <code>ServletConfig</code> 接口。* <code>GenericServlet</code> 可以直接扩展,尽管更常见的做法是扩展一个协议特定的子类,如* <code>HttpServlet</code>。** <p>* <code>GenericServlet</code> 让编写servlet变得更加容易。它提供了* <code>init</code> 和 <code>destroy</code> 生命周期方法以及* <code>ServletConfig</code> 接口中的方法的简单版本。<code>GenericServlet</code> 还实现了* <code>ServletContext</code> 接口中的 <code>log</code> 方法。** <p>* 要编写一个通用servlet,只需要重载 <code>service</code> 方法。*** @author Various*/
public abstract class GenericServlet implements Servlet, ServletConfig, java.io.Serializable {private static final long serialVersionUID = -8592279577370996712L;private static final String LSTRING_FILE = "jakarta.servlet.LocalStrings";private static ResourceBundle lStrings = ResourceBundle.getBundle(LSTRING_FILE);private transient ServletConfig config;/**** 不做任何操作。所有的servlet初始化都在其中一个* <code>init</code> 方法中完成。**/public GenericServlet() {}/*** 由servlet容器调用,通知servlet正在被移出服务。参见* {@link Servlet#destroy}。** */@Overridepublic void destroy() {}/*** 返回一个包含命名初始化参数的 <code>String</code>,如果参数不存在,则返回 <code>null</code>。参见* {@link ServletConfig#getInitParameter}。** <p>* 此方法用于方便。它从servlet的* <code>ServletConfig</code> 对象获取值。** @param name 一个 <code>String</code>,表示命名参数的名称** @return String 一个 <code>String</code>,包含初始化参数的值**/@Overridepublic String getInitParameter(String name) {ServletConfig sc = getServletConfig();if (sc == null) {throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));}return sc.getInitParameter(name);}/*** 返回servlet的初始化参数的名称作为<codeEnumeration</code> of <code>String</code>* 对象,如果servlet没有初始化参数,则返回一个空的<codeEnumeration</code>。参见* {@link ServletConfig#getInitParameterNames}。** <p>* 此方法用于方便。它从servlet的* <code>ServletConfig</code> 对象获取参数名称。*** @return Enumeration 一个 <code>Enumeration</code> of <code>String</code>,包含servlet的初始化参数的名称*/@Overridepublic Enumeration<String> getInitParameterNames() {ServletConfig sc = getServletConfig();if (sc == null) {throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));}return sc.getInitParameterNames();}/*** 返回此servlet的 {@link ServletConfig} 对象。** @return ServletConfig 用于此servlet的 <code>ServletConfig</code> 对象*/@Overridepublic ServletConfig getServletConfig() {return config;}/*** 返回在此servlet运行的 {@link ServletContext} 对象。参见* {@link ServletConfig#getServletContext}。** <p>* 此方法用于方便。它从servlet的* <code>ServletConfig</code> 对象获取上下文。*** @return ServletContext 一个 <code>ServletContext</code> 对象,由此servlet的 <code>init</code> 方法传递给此servlet*/@Overridepublic ServletContext getServletContext() {ServletConfig sc = getServletConfig();if (sc == null) {throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));}return sc.getServletContext();}/*** 返回有关servlet的信息,例如作者、版本和版权。默认情况下,重写此方法以使其返回有意义的结果。*** @return String 有关servlet的信息,按默认情况下为空字符串*/@Overridepublic String getServletInfo() {return "";}/*** 由servlet容器调用,以指示servlet正在被放置到服务中。参见* {@link Servlet#init}。** <p>* 该实现存储从servlet容器接收到的 <code>ServletConfig</code> 对象以供以后使用。* 覆写此形式的函数时,调用 <code>super.init(config)</code>。** @param config 传递给此servlet的 <code>ServletConfig</code> 对象** @exception ServletException 如果中断servlet的正常操作的异常发生,请参见*            {@link UnavailableException}** @see UnavailableException*/@Overridepublic void init(ServletConfig config) throws ServletException {this.config = config;this.init();}/*** 是一个方便的方法,可以被重写,这样就不需要调用 <code>super.init(config)</code>。** <p>* 与其重写稍上面的 {@link #init(ServletConfig)} 方法,不如重写此方法。默认情况下,此方法会调用* <code>super.init(config)</code> 并提供便利方法,如获取* <code>ServletConfig</code> 对象。** <p>* 重写此方法时,仍然可以获取* <code>ServletConfig</code> 对象,如 {@link #getServletConfig}。** @exception ServletException 如果中断servlet的正常操作的异常发生** @see UnavailableException*/public void init() throws ServletException {}/*** 将指定的消息写入servlet日志文件,前缀是servlet的名称。参见* {@link ServletContext#log(String)}。** @param msg 一个 <code>String</code>,表示要写入日志文件的消息*/public void log(String msg) {getServletContext().log(getServletName() + ": " + msg);}/*** 写入一个说明错误或异常的解释性消息以及与此错误或异常关联的堆栈跟踪到servlet日志文件中,前缀是servlet的名称。* 参见 {@link ServletContext#log(String, Throwable)}。*** @param message 一个 <code>String</code>,描述错误或异常** @param t 错误或异常的 <code>java.lang.Throwable</code> 异常*/public void log(String message, Throwable t) {getServletContext().log(getServletName() + ": " + message, t);}/*** 由servlet容器调用以允许servlet对请求做出响应。参见 {@link Servlet#service}。** <p>* 为此类以及 {@link HttpServlet} 等子类声明抽象的默认方法。** @param req 传递给此servlet的 <code>ServletRequest</code> 对象** @param res 传递给此servlet的 <code>ServletResponse</code> 对象** @exception ServletException 如果中断servlet的正常操作的异常发生** @exception IOException 如果输入或输出异常发生*/@Overridepublic abstract void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;/*** 返回此servlet实例的名称。参见 {@link ServletConfig#getServletName}。** @return 一个 <code>String</code>,表示此servlet实例的名称*/@Overridepublic String getServletName() {ServletConfig sc = getServletConfig();if (sc == null) {throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));}return sc.getServletName();}
}

上面的代码逻辑很简单,就不做进一步的介绍了,我们的关注点是void service(ServletRequest req, ServletResponse res)函数,所以需要进一步在子类jakarta.servlet.http.HttpServlet中查找相应的实现逻辑:

    /*** 接收标准HTTP请求,并将其分发到本类中定义的 <code>do</code><i>XXX</i> 方法。这是针对HTTP的《jakarta.servlet.Servlet》类的 <code>service</code> 方法的特定版本。* 无需重写此方法。** @param req 包含客户端对 servlet 发出的请求的 {@link HttpServletRequest} 对象** @param resp 包含 servlet 返回给客户端的响应的 {@link HttpServletResponse} 对象** @throws IOException 如果 servlet 在处理HTTP请求时发生输入或输出错误** @throws ServletException 如果无法处理HTTP请求* * @see jakarta.servlet.Servlet#service*/protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {String method = req.getMethod();if (method.equals(METHOD_GET)) {long lastModified = getLastModified(req);if (lastModified == -1) {// servlet 不支持 if-modified-since, 没有必要进行更昂贵的逻辑处理doGet(req, resp);} else {long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);if (ifModifiedSince < lastModified) {// 如果 servlet 修改时间晚于 if-modified-since 的值,调用 doGet()// 向下舍入到最近的整秒以进行正确的比较// 如果 if-modified-since 的值为 -1,则始终较小maybeSetLastModified(resp, lastModified);doGet(req, resp);} else {resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);}}} else if (method.equals(METHOD_HEAD)) {long lastModified = getLastModified(req);maybeSetLastModified(resp, lastModified);doHead(req, resp);} else if (method.equals(METHOD_POST)) {doPost(req, resp);} else if (method.equals(METHOD_PUT)) {doPut(req, resp);} else if (method.equals(METHOD_DELETE)) {doDelete(req, resp);} else if (method.equals(METHOD_OPTIONS)) {doOptions(req, resp);} else if (method.equals(METHOD_TRACE)) {doTrace(req, resp);} else {//// 注意,这意味着没有任何 servlet 在此服务器上支持请求的任何方法//String errMsg = lStrings.getString("http.method_not_implemented");Object[] errArgs = new Object[1];errArgs[0] = method;errMsg = MessageFormat.format(errMsg, errArgs);resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);}}

这段Java函数是一个HTTP请求处理方法,根据请求的HTTP请求方法调用相应的doXXX方法进行处理。如果请求方法是GET,则执行doGet方法。如果请求方法是HEAD,则执行doHead。如果请求方法是POST,则执行doPost方法。如果请求方法是PUT,则执行doPut方法。如果请求方法是DELETE,则执行doDelete方法。如果请求方法是OPTIONS,则执行doOptions方法。如果请求方法是TRACE,则执行doTrace方法。如果请求方法不被支持,则返回HTTP 501 Not Implemented错误。

jakarta.servlet.http.HttpServlet类中只做了简单的处理,源码如下:

protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {// 处理GET请求String protocol = req.getProtocol();String msg = lStrings.getString("http.method_get_not_supported");resp.sendError(getMethodNotSupportedCode(protocol), msg);
}protected long getLastModified(HttpServletRequest req) {// 获取HttpServletRequest对象的最后修改时间return -1;
}protected void doHead(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {// 处理HEAD请求if (legacyHeadHandling) {NoBodyResponse response = new NoBodyResponse(resp);doGet(req, response);response.setContentLength();} else {doGet(req, resp);}
}protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {// 处理POST请求String protocol = req.getProtocol();String msg = lStrings.getString("http.method_post_not_supported");resp.sendError(getMethodNotSupportedCode(protocol), msg);
}protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {// 处理PUT请求String protocol = req.getProtocol();String msg = lStrings.getString("http.method_put_not_supported");resp.sendError(getMethodNotSupportedCode(protocol), msg);
}protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {// 处理DELETE请求String protocol = req.getProtocol();String msg = lStrings.getString("http.method_delete_not_supported");resp.sendError(getMethodNotSupportedCode(protocol), msg);
}/*** 受服务器(通过`service`方法)调用,允许 servlet 处理 OPTIONS 请求。** OPTIONS 请求确定服务器支持哪些 HTTP 方法,并返回相应的头部。例如,如果 servlet 重写了 `doGet` 方法,* 此方法返回以下头部:** <p>`Allow: GET, HEAD, TRACE, OPTIONS`** <p无需覆盖此方法,除非 servlet 实现了除 HTTP 1.1 支持的其他 HTTP 方法。** @param req 包含客户端对 servlet 的请求的 {@link HttpServletRequest} 对象** @param resp 包含 servlet 返回给客户端的响应的 {@link HttpServletResponse} 对象** @throws IOException 如果在 servlet 处理 OPTIONS 请求时发生输入或输出错误** @throws ServletException 如果无法处理 OPTIONS 请求*/protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {Method[] methods = getAllDeclaredMethods(this.getClass());boolean ALLOW_GET = false;boolean ALLOW_HEAD = false;boolean ALLOW_POST = false;boolean ALLOW_PUT = false;boolean ALLOW_DELETE = false;boolean ALLOW_TRACE = true;boolean ALLOW_OPTIONS = true;for (int i = 0; i < methods.length; i++) {String methodName = methods[i].getName();if (methodName.equals("doGet")) {ALLOW_GET = true;ALLOW_HEAD = true;} else if (methodName.equals("doPost")) {ALLOW_POST = true;} else if (methodName.equals("doPut")) {ALLOW_PUT = true;} else if (methodName.equals("doDelete")) {ALLOW_DELETE = true;}}// 当调用此方法时,我们知道 "allow" 不为 nullStringBuilder allow = new StringBuilder();if (ALLOW_GET) {allow.append(METHOD_GET);}if (ALLOW_HEAD) {if (allow.length() > 0) {allow.append(", ");}allow.append(METHOD_HEAD);}if (ALLOW_POST) {if (allow.length() > 0) {allow.append(", ");}allow.append(METHOD_POST);}if (ALLOW_PUT) {if (allow.length() > 0) {allow.append(", ");}allow.append(METHOD_PUT);}if (ALLOW_DELETE) {if (allow.length() > 0) {allow.append(", ");}allow.append(METHOD_DELETE);}if (ALLOW_TRACE) {if (allow.length() > 0) {allow.append(", ");}allow.append(METHOD_TRACE);}if (ALLOW_OPTIONS) {if (allow.length() > 0) {allow.append(", ");}allow.append(METHOD_OPTIONS);}resp.setHeader("Allow", allow.toString());}/*** 受服务器(通过`service`方法)调用,允许 servlet 处理 TRACE 请求。** TRACE 会将 TRACE 请求的头部返回给客户端,以便在调试时使用。无需覆盖此方法。** @param req 包含客户端对 servlet 的请求的 {@link HttpServletRequest} 对象*** @param resp 包含 servlet 返回给客户端的响应的 {@link HttpServletResponse} 对象** @throws IOException 如果在 servlet 处理 TRACE 请求时发生输入或输出错误** @throws ServletException 如果无法处理 TRACE 请求*/protected void doTrace(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {int responseLength;String CRLF = "\r\n";StringBuilder buffer = new StringBuilder("TRACE ").append(req.getRequestURI()).append(" ").append(req.getProtocol());Enumeration<String> reqHeaderEnum = req.getHeaderNames();while (reqHeaderEnum.hasMoreElements()) {String headerName = reqHeaderEnum.nextElement();buffer.append(CRLF).append(headerName).append(": ").append(req.getHeader(headerName));}buffer.append(CRLF);responseLength = buffer.length();resp.setContentType("message/http");resp.setContentLength(responseLength);ServletOutputStream out = resp.getOutputStream();out.print(buffer.toString());}

根据上述的源码可知,HttpServlet类中根据支持的HTTP方法分别提供了相应的服务方法,会根据请求的不同形式将程序引导到对应的函数进行处理。这几个函数中最常用的函数主要是doGet、doPost、doDelete和doPut,上面的源码中这几个函数在响应结果中直接返回方法未实现的错误信息。这说明,doGet、doPost、doDelete和doPut函数必须要在子类中实现相应的逻辑,才能确保可用。

但是,我们分析了一下org.springframework.web.servlet.FrameworkServlet的源码后发现,该类作为HttpServlet的子类并不仅重写doGet、doPost、doDelete和doPut方法,也重写了void service(HttpServletRequest request, HttpServletResponse response)方法,源码如下:

	/*** 重写父类实现以拦截使用PATCH或非标准HTTP方法(WebDAV)的请求。*/@Overrideprotected void service(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {if (HTTP_SERVLET_METHODS.contains(request.getMethod())) {super.service(request, response);}else {processRequest(request, response);}}

该函数针对DELETE、HEAD、GET、OPTIONS、POST、PUT和TRACE方法的HTTP请求是调用HttpServlet类的service方法进行处理,其他HTTP方法的请求则是调用processRequest函数进行处理。继续看一下doGet、doPost、doDelete和doPut方法的实现源码如下:

	/*** 代理GET请求到processRequest/doService方法。* <p>也会被HttpServlet的默认实现的{@code doHead}方法调用,* 使用{@code NoBodyResponse}只捕获内容长度。* @see #doService* @see #doHead*/@Overrideprotected final void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {processRequest(request, response);}/*** 代理POST请求到{@link #processRequest}方法。* @see #doService*/@Overrideprotected final void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {processRequest(request, response);}/*** 代理PUT请求到{@link #processRequest}方法。* @see #doService*/@Overrideprotected final void doPut(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {processRequest(request, response);}/*** 代理DELETE请求到{@link #processRequest}方法。* @see #doService*/@Overrideprotected final void doDelete(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {processRequest(request, response);}

在FrameworkServlet类中,DELETE、GET、POST、PUT方法的HTTP请求也都是通过调用processRequest函数来实现的,接下来,我们就需要着重的分析processRequest函数的源码了。

/*** 处理当前请求,无论处理结果如何,都发布一个事件。* <p>实际的事件处理由抽象方法 {@link #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(request, response);}catch (ServletException | IOException ex) {failureCause = ex;throw ex;}catch (Throwable ex) {failureCause = ex;throw new ServletException("Request processing failed: " + ex, ex);}finally {resetContextHolders(request, previousLocaleContext, previousAttributes);if (requestAttributes != null) {requestAttributes.requestCompleted();}logResult(request, response, failureCause, asyncManager);publishRequestHandledEvent(request, response, startTime, failureCause);}
}

该函数已经开始了对请求的处理,虽然把实现细节委托给doService函数中实现,但是不难看出处理请求前后所做的准备与处理工作。继续查看doService函数的实现逻辑,源码如下:

/*** 对于DispatcherServlet特定的请求属性进行暴露,并委托给方法doDispatch进行实际的调度。*/
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {logRequest(request);// 保存请求属性的快照,以备包含情况时使用,以便在包含结束后恢复原始属性。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));}}}// 使处理程序和视图对象能够访问框架对象。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 {doDispatch(request, response);} finally {if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {// 如果是包含请求,则恢复原始属性快照。if (attributesSnapshot != null) {restoreAttributesAfterInclude(request, attributesSnapshot);}}if (this.parseRequestPath) {ServletRequestPathUtils.setParsedRequestPath(previousRequestPath, request);}}
}

这个函数是一个重写的doService函数,它在DispatcherServlet中使用。它的目的是在实际进行调度之前,提供DispatcherServlet特定的请求属性,并在处理完毕后进行日志记录和属性的恢复。函数中还包含了一些其他操作,如设置框架对象、处理FlashMap和解析请求路径。核心的处理逻辑是放在doDispatch函数中进行处理的,源码如下:

/*** 处理实际的调度到处理程序。处理程序将通过servlet的HandlerMappings获取。* HandlerAdapter将通过查询servlet安装的HandlerAdapters获取第一个支持处理程序类的处理程序。* <p>所有HTTP方法均由本方法处理。由HandlerAdapters或处理程序本身决定哪些方法是可接受的。* @param request 当前 HTTP 请求* @param response 当前 HTTP 响应* @throws Exception 在任何类型的处理失败情况下抛出*/
@SuppressWarnings("deprecation")
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {// 如果是MultipartContent类型的request则将request转换为MultipartHttpServletRequest类型的requestHttpServletRequest processedRequest = checkMultipart(request);boolean multipartRequestParsed = (processedRequest != request);// 获取异步管理器WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);try {ModelAndView mv = null;Exception dispatchException = null;try {// 根据request信息寻找对应的HandlerHandlerExecutionChain mappedHandler = getHandler(processedRequest);if (mappedHandler == null) {noHandlerFound(processedRequest, response);return;}// 根据当前的handler寻找对应的HandlerAdapterHandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());// 如果当前handler支持last-modified头处理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;}}// 拦截器的preHandle方法的调用if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}// 实际调用处理程序并返回视图mv = ha.handle(processedRequest, response, mappedHandler.getHandler());// 如果异步处理已开始,则返回if (asyncManager.isConcurrentHandlingStarted()) {return;}// 视图名称转换应用于需要添加前缀后缀的情况applyDefaultViewName(processedRequest, mv);// 应用所有拦截器的postHandle方法mappedHandler.applyPostHandle(processedRequest, response, mv);} catch (Exception ex) {dispatchException = ex;} catch (Throwable err) {// 将处理从处理程序方法抛出的Error,使其可供@ExceptionHandler方法和其他场景使用。dispatchException = new ServletException("Handler dispatch failed: " + err, err);}// 处理调度结果processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);} catch (Exception ex) {triggerAfterCompletion(processedRequest, response, mappedHandler, ex);} catch (Throwable err) {triggerAfterCompletion(processedRequest, response, mappedHandler,new ServletException("Handler processing failed: " + err, err));} finally {// 如果异步处理已开始,则执行 afterConcurrentHandlingStartedif (asyncManager.isConcurrentHandlingStarted()) {if (mappedHandler != null) {mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);}} else {// 清理任何用于 multipart 请求的资源if (multipartRequestParsed) {cleanupMultipart(processedRequest);}}}
}

这个函数是一个核心的请求处理方法,它通过执行以下步骤来处理HTTP请求:检查请求是否包含文件(多部分解析)、确定处理器(Handler)和处理器适配器、处理最后修改头、执行预处理、处理处理器适配器调用处理程序方法、应用默认视图名称、执行后处理、处理并处理调度结果。如果出现异常,它会触发完成处理。最后,它会清理任何由多部分请求使用的资源。

 根据request获取异步管理器

/*** 获取当前请求的 {@link WebAsyncManager},如果未找到则创建并将其与请求关联起来。*/
public static WebAsyncManager getAsyncManager(ServletRequest servletRequest) {WebAsyncManager asyncManager = null;Object asyncManagerAttr = servletRequest.getAttribute(WEB_ASYNC_MANAGER_ATTRIBUTE);if (asyncManagerAttr instanceof WebAsyncManager wam) {asyncManager = wam;}if (asyncManager == null) {asyncManager = new WebAsyncManager();servletRequest.setAttribute(WEB_ASYNC_MANAGER_ATTRIBUTE, asyncManager);}return asyncManager;
}

MultipartContent类型的request处理

对于请求的处理,Spring首先考虑的是对于Multipart的处理,如果是MultipartContent类型的request,则将request转换为MultipartHttpServletRequest类型的request。源码如下:

/*** 将请求转换为多部分请求,并使多部分解析器可用。* <p>如果没有设置多部分解析器,则直接使用现有的请求。* @param request 当前HTTP请求* @return 处理后的请求(如果需要,多部分包装器)* @see MultipartResolver#resolveMultipart*/
protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {if (DispatcherType.REQUEST.equals(request.getDispatcherType())) {logger.trace("请求已解析为MultipartHttpServletRequest,例如由MultipartFilter");}}else if (hasMultipartException(request)) {logger.debug("当前请求的多部分解析之前失败 - 为无干扰的错误渲染而跳过重新解析");}else {try {return this.multipartResolver.resolveMultipart(request);}catch (MultipartException ex) {if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) != null) {logger.debug("多部分解析在错误分发时失败", ex);// 在下面继续处理错误分发时的错误处理请求}else {throw ex;}}}}// 如果之前没有返回:返回原始请求。return request;
}

这个函数用于将请求转换为多部分请求,并提供多部分解析器。如果设置了多部分解析器且请求是多部分请求,则将其解析为多部分HTTP请求。如果解析失败并且请求是错误处理,则跳过重新解析。如果没有设置多部分解析器,则直接返回原始请求。

我们可以参考一下org.springframework.web.multipart.support.StandardServletMultipartResolver类中resolveMultipart函数的实现逻辑:

@Overridepublic MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException {return new StandardMultipartHttpServletRequest(request, this.resolveLazily);}

根据request信息寻找对应的HandlerExecutionChain

/*** 返回对此请求的HandlerExecutionChain。* <p>按顺序尝试所有的处理器映射。* @param request 当前HTTP请求* @return 处理器执行链,如果找不到处理器则返回null* @throws Exception 如果获取处理器出错*/
@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寻找对应的HandlerAdapter

/*** 获取该handler对象的HandlerAdapter。* @param handler 需要查找适配器的handler对象* @throws ServletException 如果找不到适用于该handler的HandlerAdapter,这将是一个致命错误。*/
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {if (this.handlerAdapters != null) {for (HandlerAdapter adapter : this.handlerAdapters) {if (adapter.supports(handler)) {return adapter;}}}throw new ServletException("No adapter for handler [" + handler +"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}

在默认情况下普通的web请求会交给SimpleControllerHandlerAdapter去处理,SimpleControllerHandlerAdapter源码如下:

/*** Adapter to use the plain {@link Controller} workflow interface with* the generic {@link org.springframework.web.servlet.DispatcherServlet}.* Supports handlers that implement the {@link LastModified} interface.** <p>This is an SPI class, not used directly by application code.** @author Rod Johnson* @author Juergen Hoeller* @see org.springframework.web.servlet.DispatcherServlet* @see Controller* @see HttpRequestHandlerAdapter*/
public class SimpleControllerHandlerAdapter implements HandlerAdapter {/*** 判断是否支持该处理器类* @param handler 处理器对象* @return 如果支持,则返回`true`,否则返回`false`*/@Overridepublic boolean supports(Object handler) {return (handler instanceof Controller);}/*** 处理HTTP请求* @param request HTTP请求对象* @param response HTTP响应对象* @param handler 处理器对象* @return 处理结果对象* @throws Exception 如果处理过程中发生异常*/@Override@Nullablepublic ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {return ((Controller) handler).handleRequest(request, response);}/*** 获取资源最后修改时间* @param request HTTP请求对象* @param handler 处理器对象* @return 最后修改时间,单位为毫秒;如果资源未修改,则返回-1*/@Override@SuppressWarnings("deprecation")public long getLastModified(HttpServletRequest request, Object handler) {if (handler instanceof LastModified lastModified) {return lastModified.getLastModified(request);}return -1L;}}

SimpleControllerHandlerAdapter就是用于处理普通的Web请求的,而且对于SpringMVC来说,我们会把逻辑封装到Controller的子类中。

HandlerInterceptor的处理

·Servlet API定义的servlet过滤器可以在servlet处理每个Web请求的前后分别对它进行前置和后置处理。此外,有些时候,你可能只想处理由某些SpringMVC处理程序处理

/*** Apply preHandle methods of registered interceptors.* @return {@code true} if the execution chain should proceed with the* next interceptor or the handler itself. Else, DispatcherServlet assumes* that this interceptor has already dealt with the response itself.*/
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {for (int i = 0; i < this.interceptorList.size(); i++) {HandlerInterceptor interceptor = this.interceptorList.get(i);if (!interceptor.preHandle(request, response, this.handler)) {triggerAfterCompletion(request, response, null);return false;}this.interceptorIndex = i;}return true;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/586923.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【网络安全】网络隔离设备

一、网络和终端隔离产品 网络和终端隔离产品分为终端隔离产品和网络隔离产品两大类。终端隔离产品一般指隔离卡或者隔离计算机。网络隔离产品根据产品形态和功能上的不同&#xff0c;该类产品可以分为协议转换产品、网闸和网络单向导入产品三种。 图1为终端隔离产品的一个典型…

dash 中的模式匹配回调函数Pattern-Matching Callbacks 8

模式匹配 模式匹配回调选择器 MATCH、ALL 和 ALLSMALLER 允许您编写可以响应或更新任意或动态数量组件的回调函数。 此示例呈现任意数量的 dcc. Dropdown 元素&#xff0c;并且只要任何 dcc. Dropdown 元素发生更改&#xff0c;就会触发回调。尝试添加几个下拉菜单并选择它们的…

Grafana增加仪表盘

1.Grafana介绍 grafana 是一款采用Go语言编写的开源应用&#xff0c;主要用于大规模指标数据的可视化展现&#xff0c;是网络架构和应用分析中最流行的时序数据展示工具&#xff0c;目前已经支持绝大部分常用的时序数据库。 Grafana下载地址&#xff1a;https://grafana.com/g…

burpsuite的安装与介绍

安装(挑一个你喜欢的版本安装就行) 编程环境安装指南:Java、Python 和 Burp Suite抓包工具_burpsuite和java-CSDN博客 简介 Burp Suite是一个用于攻击Web应用程序的集成平台。它集成了多种渗透测试组件,能够帮助我们更好地完成对Web应用的渗透测试和攻击,无论是自动化还…

基于CNN神经网络的手写字符识别实验报告

作业要求 具体实验内容根据实际情况自拟&#xff0c;可以是传统的BP神经网络&#xff0c;Hopfield神经网络&#xff0c;也可以是深度学习相关内容。 数据集自选&#xff0c;可以是自建数据集&#xff0c;或MNIST&#xff0c;CIFAR10等公开数据集。 实验报告内容包括但不限于&am…

[C#]opencvsharp进行图像拼接普通拼接stitch算法拼接

介绍&#xff1a; opencvsharp进行图像拼一般有2种方式&#xff1a;一种是传统方法将2个图片上下或者左右拼接&#xff0c;还有一个方法就是融合拼接&#xff0c;stitch拼接就是一种非常好的算法。opencv里面已经有stitch拼接算法因此我们很容易进行拼接。 效果&#xff1a; …

PayPal账户被封是因为什么?如何解决?

Paypal作为跨境出海玩家最常用的付款工具之一&#xff0c;同时也是最容易出现冻结封号现象。保障PP账号安全非常重要&#xff0c;只有支付渠道安全&#xff0c;才不会“白费力气”&#xff0c;那么最重要的就是要了解它的封号原因以做好规避。 一、Paypal账号被封原因 1、账号…

FreeRTOS列表与列表项相关知识总结以及列表项的插入与删除实战

1.列表与列表项概念及结构体介绍 1.1列表项简介 列表相当于链表&#xff0c;列表项相当于节点&#xff0c;FreeRTOS 中的列表是一个双向环形链表 1.2 列表、列表项、迷你列表项结构体 1&#xff09;列表结构体 typedef struct xLIST { listFIRST_LIST_INTEGRITY_CHECK_VAL…

List常见方法和遍历操作

List集合的特点 有序&#xff1a; 存和取的元素顺序一致有索引&#xff1a;可以通过索引操作元素可重复&#xff1a;存储的元素可以重复 List集合的特有方法 Collection的方法List都继承了List集合因为有索引&#xff0c;所以有了很多操作索引的方法 ublic static void main…

挑战 ChatGPT 和 Google Bard 的防御

到目前为止&#xff0c;科学家已经创建了基于人工智能的聊天机器人&#xff0c;可以帮助内容生成。我们还看到人工智能被用来创建像 WormGPT 这样的恶意软件&#xff0c;尽管地下社区对此并不满意。但现在正在创建聊天机器人&#xff0c;可以使用生成人工智能通过即时注入活动来…

OpenHarmony之分布式软总线

背景概述 从之前的文档(OpenHarmony之内核层)可知 分布式软总线是多设备终端的统一基座&#xff0c;为设备间的无缝互联提供了统一的分布式通信能力&#xff0c;能够快速发现并连接设备&#xff0c;高效地传输任务和数据。 分布式软总线实现近场设备间统一的分布式通信管理能…

代码随想录刷题第三十四天| 1005.K次取反后最大化的数组和 ● 134. 加油站 ● 135. 分发糖果

代码随想录刷题第三十四天 K次取反后最大化的数组和 (LC 1005) 题目思路&#xff1a; 代码实现&#xff1a; class Solution:def largestSumAfterKNegations(self, nums: List[int], k: int) -> int:nums.sort(keylambda x: abs(x), reverseTrue)for i in range(len(nums…

8868体育助力意甲罗马俱乐部 迪巴拉有望付出

8868体育助力意甲罗马俱乐部 迪巴拉有望付出 意甲罗马俱乐部是8868体育合作球队之一&#xff0c;本赛季&#xff0c;在意甲第14轮的比赛中&#xff0c;罗马客场2-1战胜萨索洛&#xff0c;积分上升到意甲第4位。 有报道称&#xff0c;迪巴拉在对阵佛罗伦萨的比赛中受伤&#xff…

网络Ping不通故障定位思路

故障分析 Ping不通是指Ping报文在网络中传输&#xff0c;由于各种原因&#xff08;如链路故障、ARP学习失败等&#xff09;而接收不到所有Ping应答报文的现象。 如图1-1所示&#xff0c;以一个Ping不通的尝试示例&#xff0c;介绍Ping不通故障的定位思路。 图1-1 Ping不通故…

在机器学习训练测试集中,如何切分出一份验证集

文章目录 1.读取数据&#xff1a;2.绘图查看target数量情况&#xff1a;3.特征拓展&#xff1a;4.构建X&#xff0c;y&#xff1a;5.拆分训练集和测试集&#xff0c;特征做缩放处理&#xff1a;6.从训练集里再切一次出验证集&#xff0c;特征做缩放处理&#xff1a;7.测试集训练…

Shell基本命令 Mkdir创建 cp 复制 ls-R递归打印 文件权限

mkdir 是在命令行界面下创建目录&#xff08;文件夹&#xff09;的命令。它是 “make directory” 的缩写。 以下是 mkdir 命令的基本语法&#xff1a; mkdir 目录路径其中&#xff0c;目录路径 是要创建的目录的路径和名称。 例如&#xff0c;要在当前目录下创建一个名为 m…

Java介绍

Java 是一门纯粹的面向对象编程语言&#xff0c;它吸收了C的各种优点&#xff0c;还努力摒弃了C里难以理解的多继承、指针等概念&#xff0c;真正地实现了面向对象理论&#xff0c;因而具有功能强大和简单易用两个特征。 除了基础语法之外&#xff0c;Java还有许多必须弄懂的特…

OpenCV-Python(22):直方图的计算绘制与分析

目标 了解直方图的原理及应用使用OpenCV 或Numpy 函数计算直方图使用Opencv 或者Matplotlib 函数绘制直方图学习函数cv2.calcHist()、np.histogram()等 原理及应用 直方图是一种统计图形&#xff0c;是对图像的另一种解释&#xff0c;用于表示图像中各个像素值的频次分布。直…

sklearn 中matplotlib编制图表

代码 # 导入pandas库&#xff0c;并为其设置别名pd import pandas as pd import matplotlib.pyplot as plt# 使用pandas的read_csv函数读取名为iris.csv的文件&#xff0c;将数据存储在iris_data变量中 iris_data pd.read_csv(data/iris.txt,sep\t)# 使用groupby方法按照&quo…

人机交互中信息数量与质量

在人机交互中&#xff0c;信息的数量和质量都是非常重要的因素。 信息的数量指的是交互过程中传递的信息的多少。信息的数量直接影响到交互的效率和效果&#xff0c;如果交互中传递的信息量太少&#xff0c;可能导致交互过程中的信息不足&#xff0c;用户无法得到想要的结果或者…