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}。
     *
     * 
     */
    @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中查找相应的实现逻辑:

    /**
     * 接收标准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" 不为 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)方法,源码如下:

	/**
	 * 重写父类实现以拦截使用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方法的实现源码如下:

	/**
	 * 代理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函数的源码了。

/**
 * 处理当前请求,无论处理结果如何,都发布一个事件。
 * <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类型的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获取异步管理器

/**
 * 获取当前请求的 {@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函数的实现逻辑:


	@Override
	public 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`
	 */
	@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处理程序处理

/**
 * 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;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

tcpdump出现permission denied

在使用tcpdump -i eth0 src host 192.168.0.184 and ip and port 22 -nn -w ping.pacp命令抓包并把抓到的数据保存到ping.pacp时&#xff0c;出现了权限错误的报错。但实际上我这里用的是root用户执行的命令。 查阅man手册发现: 在tcpdump中&#xff0c;-Z选项用于在启动数据…

思维训练-怎样设计一个MQ

架构师需要做各种设计&#xff0c;要不断地提高自己的设计能力。这有没有方法可以训练呢&#xff1f;有的&#xff0c;就是看到什么、想到什么&#xff0c;就假设对面坐着产品经理&#xff0c;一起讨论怎么把它设计出来。比如怎样设计一个MQ 我&#xff1a;首先我确认一下需求。…

nodejs+vue+ElementUi摄影预约服务网站系统91f0v

本系统提供给管理员对首页&#xff0c;个人中心&#xff0c;用户管理&#xff0c;员工管理&#xff0c;摄影套餐管理&#xff0c;套餐系列管理&#xff0c;客片欣赏管理&#xff0c;摄影预约管理&#xff0c;摄影订单管理&#xff0c;取片通知管理&#xff0c;摄影评价管理&…

优化算法3D可视化

编程实现优化算法&#xff0c;并3D可视化 1. 函数3D可视化 分别画出 和 的3D图 import numpy as np from matplotlib import pyplot as plt import torch# 画出x**2 class Op(object):def __init__(self):passdef __call__(self, inputs):return self.forward(inputs)def for…

分布式系统架构设计之分布式数据存储的分类和组合策略

在现下科技发展迅猛的背景下&#xff0c;分布式系统已经成为许多大规模应用和服务的基础架构。分布式架构的设计不仅仅是一项技术挑战&#xff0c;更是对数据存储、管理和处理能力的严峻考验。随着云原生、大数据、人工智能等技术的崛起&#xff0c;分布式系统对于数据的高效存…

Springer build pdf乱码

在textstudio中编辑时没有错误&#xff0c;在editor manager生成pdf时报错。 首先不要改源文件&#xff0c;着重看你的上传顺序&#xff1a; 将.tex文件&#xff0c;.bst文件&#xff0c;.cls文件&#xff0c;.bib文件, .bbl文件的类型&#xff0c;在editor manager中是Item。…

Hive集群出现报错信息解决办法

一、报错信息&#xff1a;hive> show databases;FAILED: HiveException java.lang.RuntimeException: Unable to instantiate org.apache.hadoop.hive.ql.metadata.SessionHiveMetaStoreClient 解决办法&#xff1a;1.删除mysql中的元数据库&#xff08;metastore&#xff0…

【owt-server】一些构建项目梳理

【owt-server】清理日志&#xff1a;owt、srs、ffmpeg 【owt】p2p client mfc 工程梳理【m98】webrtc vs2017构建带符号的debug库【OWT】梳理构建的webrtc和owt mfc工程 m79的mfc客户端及owt-client

代码随想录刷题第三十五天| 860.柠檬水找零 ● 406.根据身高重建队列 ● 452. 用最少数量的箭引爆气球|

代码随想录刷题第三十五天 柠檬水找零 (LC 860) 题目思路&#xff1a; 代码实现&#xff1a; class Solution:def lemonadeChange(self, bills: List[int]) -> bool:five 0ten 0for money in bills:if money 5:five1if money 10:if five>0:five-1ten1else:return …

基于FPGA的数字电路(PWM脉宽调制)

一.PWM的制作原理 假如我们有一个锯齿波&#xff0c;然后在锯齿波上设置一个阈值&#xff08;黑色水平虚线&#xff09;&#xff0c;凡是大于该阈值时输出均为高电平&#xff0c;反之则为低电平&#xff0c;这样我们是不是就得到一个PWM信号呢&#xff1f;如果我们想调整它的占…

数据库中的锁

锁 锁冲突是针对事务的&#xff0c;另一个事务能不能申请上锁&#xff0c;是和其他事务竞争。 一个事务内部可以加很多锁&#xff0c;互相并不会冲突。 级联回滚调度 多个事务有依赖关系&#xff0c;如果一个事务回滚&#xff0c;那么所有事务也需要回滚。 冲突的数据加锁安…

【力扣题解】P106-从中序与后序遍历序列构造二叉树-Java题解

&#x1f468;‍&#x1f4bb;博客主页&#xff1a;花无缺 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! 本文由 花无缺 原创 收录于专栏 【力扣题解】 文章目录 【力扣题解】P106-从中序与后序遍历序列构造二叉树-Java题解&#x1f30f;题目描述&#x1f4a1;题…

Linux:apache优化(1)—— 长链接/保持连接

系统:CentOS 7.9 apache版本为&#xff1a;2.4.25 需要使用源码包进行安装才能够使用这些扩展模块 在使用这些扩展模块前要先下载zlib-devel 安装--enable-deflate选项需要的网页压缩传输的软件包 yum -y install zlib-devel 在配置编译安装时需要使用扩展配置 ./config…

模式识别与机器学习-集成学习

集成学习 集成学习思想过拟合与欠拟合判断方法 K折交叉验证BootstrapBagging随机森林的特点和工作原理&#xff1a; BoostingAdaBoost工作原理&#xff1a;AdaBoost的特点和优点&#xff1a;AdaBoost的缺点&#xff1a; Gradient Boosting工作原理&#xff1a;Gradient Boostin…

【机器学习合集】深度生成模型 ->(个人学习记录笔记)

深度生成模型 深度生成模型基础 1. 监督学习与无监督学习 1.1 监督学习 定义 在真值标签Y的指导下&#xff0c;学习一个映射函数F&#xff0c;使得F(X)Y 判别模型 Discriminative Model&#xff0c;即判别式模型&#xff0c;又称为条件模型&#xff0c;或条件概率模型 生…

Linux驱动开发简易流程

推荐视频&#xff1a; 正点原子【第四期】手把手教你学 Linux之驱动开发篇 小智-学长嵌入式Linux&Android底层开发入门教程 能力矩阵 基础能力矩阵 熟悉c/c、熟悉数据结构 熟悉linux系统&#xff0c;Shell脚本&#xff0c;Makefile/cmake/mk 文件IO、多线程、竞争、并发…

基于Python的B站排行榜大数据分析与可视化系统

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长 QQ 名片 :) 1. 项目简介 本文介绍了一项基于Python的B站排行榜大数据分析与可视化系统的研究。通过网络爬虫技术&#xff0c;系统能够自动分析B站网址&#xff0c;提取大量相关文本信息并存储在系统中。通过对这些信息进行…

nginx+keepalived实现七层负载

目录 一、部署nginx01、nginx02 二、keepalived配置&#xff08;抢占模式、master- backup模式&#xff09; 三、测试 四、非抢占模式&#xff08;backup-backup模式&#xff09; nginx01 11.0.1.31nginx0211.0.1.32虚拟IP&#xff08;VIP&#xff09;11.0.1.30 一、部署ngin…

Android实验:contentprovider 实验+SQLite 数据库的实现

目录 SQLite实验目的实验内容实验要求项目结构代码实现结果展示 SQLite SQLite 是一个开源的嵌入式关系数据库&#xff0c;实现了自给自足的、无服务器的、配置无需的、事务性的 SQL 数据库引擎。它是一个零配置的数据库&#xff0c;这意味着与其他数据库系统不同&#xff0c;…

虚拟化技术和云计算的关系

1、云计算底层就是虚拟化技术。 &#xff08;1&#xff09;常见的虚拟化技术&#xff1a;VMware&#xff08;闭源的&#xff0c;需要收费&#xff09;、XEN、KVM &#xff08;2&#xff09;大部分公司用的虚拟化方案&#xff1a;XEN、KVM 2、虚拟化的历史 &#xff08;1&am…