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

DispatcherServlet类相关的结构图如下:

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

java 复制代码
/**
 * 定义所有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类的源码如下:

java 复制代码
/**
 * 定义了一个通用的、协议无关的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}。
     *
     * 
     */
    @Override
    public 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>,包含初始化参数的值
     *
     */
    @Override
    public 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的初始化参数的名称
     */
    @Override
    public 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> 对象
     */
    @Override
    public 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
     */
    @Override
    public 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的信息,按默认情况下为空字符串
     */
    @Override
    public 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
     */
    @Override
    public 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 如果输入或输出异常发生
     */
    @Override
    public abstract void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;

    /**
     * 返回此servlet实例的名称。参见 {@link ServletConfig#getServletName}。
     *
     * @return 一个 <code>String</code>,表示此servlet实例的名称
     */
    @Override
    public 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中查找相应的实现逻辑:

java 复制代码
    /**
     * 接收标准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类中只做了简单的处理,源码如下:

java 复制代码
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" 不为 null
        StringBuilder 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)方法,源码如下:

java 复制代码
	/**
	 * 重写父类实现以拦截使用PATCH或非标准HTTP方法(WebDAV)的请求。
	 */
	@Override
	protected 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方法的实现源码如下:

java 复制代码
	/**
	 * 代理GET请求到processRequest/doService方法。
	 * <p>也会被HttpServlet的默认实现的{@code doHead}方法调用,
	 * 使用{@code NoBodyResponse}只捕获内容长度。
	 * @see #doService
	 * @see #doHead
	 */
	@Override
	protected final void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		processRequest(request, response);
	}

	/**
	 * 代理POST请求到{@link #processRequest}方法。
	 * @see #doService
	 */
	@Override
	protected final void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		processRequest(request, response);
	}

	/**
	 * 代理PUT请求到{@link #processRequest}方法。
	 * @see #doService
	 */
	@Override
	protected final void doPut(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		processRequest(request, response);
	}

	/**
	 * 代理DELETE请求到{@link #processRequest}方法。
	 * @see #doService
	 */
	@Override
	protected final void doDelete(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		processRequest(request, response);
	}

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

java 复制代码
/**
 * 处理当前请求,无论处理结果如何,都发布一个事件。
 * <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函数的实现逻辑,源码如下:

java 复制代码
/**
 * 对于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函数中进行处理的,源码如下:

java 复制代码
/**
 * 处理实际的调度到处理程序。处理程序将通过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类型的request
    HttpServletRequest processedRequest = checkMultipart(request);
    boolean multipartRequestParsed = (processedRequest != request);

    // 获取异步管理器
    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

    try {
        ModelAndView mv = null;
        Exception dispatchException = null;

        try {
            // 根据request信息寻找对应的Handler
            HandlerExecutionChain mappedHandler = getHandler(processedRequest);
            if (mappedHandler == null) {
                noHandlerFound(processedRequest, response);
                return;
            }

            // 根据当前的handler寻找对应的HandlerAdapter
            HandlerAdapter 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 {
        // 如果异步处理已开始,则执行 afterConcurrentHandlingStarted
        if (asyncManager.isConcurrentHandlingStarted()) {
            if (mappedHandler != null) {
                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
            }
        } else {
            // 清理任何用于 multipart 请求的资源
            if (multipartRequestParsed) {
                cleanupMultipart(processedRequest);
            }
        }
    }
}

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

根据request获取异步管理器

java 复制代码
/**
 * 获取当前请求的 {@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。源码如下:

java 复制代码
/**
 * 将请求转换为多部分请求,并使多部分解析器可用。
 * <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函数的实现逻辑:

java 复制代码
	@Override
	public MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException {
		return new StandardMultipartHttpServletRequest(request, this.resolveLazily);
	}

根据request信息寻找对应的HandlerExecutionChain

java 复制代码
/**
 * 返回对此请求的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

java 复制代码
/**
 * 获取该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源码如下:

java 复制代码
/**
 * 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`
	 */
	@Override
	public boolean supports(Object handler) {
		return (handler instanceof Controller);
	}

	/**
	 * 处理HTTP请求
	 * @param request HTTP请求对象
	 * @param response HTTP响应对象
	 * @param handler 处理器对象
	 * @return 处理结果对象
	 * @throws Exception 如果处理过程中发生异常
	 */
	@Override
	@Nullable
	public 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处理程序处理

java 复制代码
/**
 * 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;
}
相关推荐
qiyi.sky几秒前
JavaWeb——Web入门(8/9)- Tomcat:基本使用(下载与安装、目录结构介绍、启动与关闭、可能出现的问题及解决方案、总结)
java·前端·笔记·学习·tomcat
lapiii3584 分钟前
图论-代码随想录刷题记录[JAVA]
java·数据结构·算法·图论
程序员小明z7 分钟前
基于Java的药店管理系统
java·开发语言·spring boot·毕业设计·毕设
清云随笔21 分钟前
axios 实现 无感刷新方案
前端
鑫宝Code23 分钟前
【React】状态管理之Redux
前端·react.js·前端框架
爱敲代码的小冰26 分钟前
spring boot 请求
java·spring boot·后端
忠实米线31 分钟前
使用pdf-lib.js实现pdf添加自定义水印功能
前端·javascript·pdf
pink大呲花33 分钟前
关于番外篇-CSS3新增特性
前端·css·css3
少年维持着烦恼.38 分钟前
第八章习题
前端·css·html
Lyqfor39 分钟前
云原生学习
java·分布式·学习·阿里云·云原生