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

news2025/4/23 15:13:04

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

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

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

相关文章

Java EE Servlet之服务器版表白墙

文章目录 1. 准备工作2. 约定前后端交互接口3. 编写提交消息4. 数据存入文件5. 引入数据库 1. 准备工作 我们要把表白墙程序修改成服务器版本 这样即使页面关闭, 表白墙的内容也不会丢失 此处&#xff0c;服务器要实现的功能&#xff0c;主要是两个方面&#xff1a; 页面加载…

CSS 动态提示框

​​ <template> <div class"terminal-loader"><div class"terminal-header"><div class"terminal-title">提示框</div><div class"terminal-controls"><div class"control close"…

Qt 5.9.4 转 Qt 6.6.1 遇到的问题总结(一)

最近公司对大家的开发的硬件环境进行了升级&#xff0c;电脑主机的配置、显示器&#xff08;两台大屏显示器&#xff09;变得的逼格高多了。既然电脑上的开发环境都需要重装&#xff0c;就打算把开发环境也升级到最新版本&#xff0c;要用就用最新版本。下面对升级后的开发环境…

数据库开发之子查询案例的详细解析

1.5 案例 基于之前设计的多表案例的表结构&#xff0c;我们来完成今天的多表查询案例需求。 准备环境 将资料中准备好的多表查询的数据准备的SQL脚本导入数据库中。 分类表&#xff1a;category 菜品表&#xff1a;dish 套餐表&#xff1a;setmeal 套餐菜品关系表&#x…

LeetCode刷题--- 黄金矿工

个人主页&#xff1a;元清加油_【C】,【C语言】,【数据结构与算法】-CSDN博客 个人专栏 力扣递归算法题 http://t.csdnimg.cn/yUl2I 【C】 ​​​​​​http://t.csdnimg.cn/6AbpV 数据结构与算法 ​​​​http://t.csdnimg.cn/hKh2l 前言&#xff1a;这个专栏主要讲述…

2017年喜茶数字营销变化

1. 什么是数字营销&#xff1f;数字化时代&#xff0c;消费者行为模式发生了哪些变化&#xff1f; 数字营销是指使用数字渠道和平台&#xff0c;通过在线手段推广产品或服务&#xff0c;与目标受众进行互动和沟通的一种营销方式。它涵盖了多种在线渠道&#xff0c;包括社交媒…

【赠书第15期】案例学Python(基础篇)

文章目录 前言 1 简介 2 功能列表 3 实现 3.1 学生类 3.2 学生管理系统类 3.3 使用示例 4 推荐图书 5 粉丝福利 前言 当涉及案例学 Python 时&#xff0c;可以选择一个具体的问题或场景&#xff0c;通过编写代码来解决或模拟这个问题。以下是一个例子&#xff0c;通过…

自动化测试系列 之 Python单元测试框架unittest

一、概述 什么是单元测试 单元测试是一种软件测试方法&#xff0c;是测试最小的可测试单元&#xff0c;通常是一个函数或一个方法。 在软件开发过程中&#xff0c;单元测试作为一项重要的测试方法被广泛应用。 为什么需要单元测试 单元测试是软件开发中重要的一环&#xf…

HackTheBox - Medium - Linux - Interface

Interface Interface 是一种中等难度的 Linux 机器&#xff0c;具有“DomPDF”API 端点&#xff0c;该端点通过将“CSS”注入处理后的数据而容易受到远程命令执行的影响。“DomPDF”可以被诱骗在其字体缓存中存储带有“PHP”文件扩展名的恶意字体&#xff0c;然后可以通过从其…

前端开发工具之HBuilder X

HBuilderX&#xff08;简称HX&#xff09;是一款由DCloud开发的集成开发环境&#xff08;IDE&#xff09;&#xff0c;专为前端开发者设计。它不仅是一个编辑器&#xff0c;也可以看作是一个通用的IDE&#xff0c;类似于VSCode、Sublime和WebStorm。HBuilderX支持开发各种Web项…

论文阅读——EfficientViT(cvpr2023)

EfficientViT: Memory Efficient Vision Transformer with Cascaded Group Attention 1、 从三个角度探讨如何提高vision transformers的效率&#xff1a;内存访问、计算冗余和参数使用。 2.1. Memory Efficiency 红色字体表示操作所花费的时间主要由内存访问决定&#xff0c;…

RSA加密解密——用shell加密java解密

功能描述 使用shell opensll对明文进行RSA加密&#xff0c;将密文用java的RSA工具对密文解密。这应该是全网第一个同时用到shell和java的RSA加密解密教程。中间有很多坑&#xff0c;都踩过了&#xff0c;可以放心使用代码。 正确的实现流程 shell端 首先生成公钥私钥 &…

C/C++ 对象、继承和引用

ostream和ofstream类凸现了引用的一个有趣属性。正如ofstream 对象可以使用 ostream类的方法&#xff0c;这使得文件输入/输出的格式与控制台输入/输出相同。使得能够将特性从一个类传递给另一个类的语言特性被称为继承。 简单地说&#xff0c;ostream 是基类&#xff0c;而ofs…

<软考高项备考>《论文专题 - 37 采购管理(1) 》

1 成本管理基础 1.1 写作要点 过程定义、作用写作要点、思路规划采购管理规划采购管理是记录项目采购决策、明确采购方法&#xff0c;及识别潜在卖方的过程。作用:确定是否从项目外部获取货物和服务&#xff0c;如果是&#xff0c;则还要确定将在什么时间、以什么方式获取什么…

OSPF的DR与BDR-新版(16)

目录 整体拓扑 操作步骤 1.基本配置 1.1 配置R1的IP 1.2 配置R2的IP 1.3 配置R3的IP 1.4 配置R4的IP 1.5 检测R1与R4连通性 1.6 检测R1与R2连通性 1.7 检测R1与R3连通性 2.搭建基本的OSPF网络 2.1 配置R1 OSPF 2.2 配置R2 OSPF 2.3 配置R3 OSPF 2.4 配置R4 OSPF…

力扣刷题总结 栈与队列

&#x1f525;博客主页&#xff1a; A_SHOWY&#x1f3a5;系列专栏&#xff1a;力扣刷题总结录 数据结构 云计算 数字图像处理 力扣每日一题_ 一、栈和队列的基础知识 队列是先进先出&#xff0c;栈是先进后出。同时二者都是容器适配器而不是容器。 二、题目实战 232.用栈…

帆软报表如何灵活控制水印的显示

在帆软报表中如果要显示水印,如果要全部都要显示,只需要到决策系统--安装设置中打开水印开关。如果想要某个报表显示水印,可以在设计器的水印设置中为该报表设置水印。 但是如果碰到这种需求,比如某些人或者某些角色需要显示水印,其他人不显示。或者是预览报表需要显示水印…

conda环境下face_alignment.LandmarksType._2D AttributeError: _2D解决方法

1 问题描述 运行retalking模型时&#xff0c;代码抛出异常&#xff0c;信息如下所示&#xff1a; Traceback (most recent call last):File "D:/ml/video-retalking/inference.py", line 345, in <module>main()File "D:/ml/video-retalking/inference.…

C++初阶(类中的默认成员函数)

呀哈喽&#xff0c;我是结衣 今天给大家带来的是类里面的默认成员函数&#xff0c;一共有六个默认的成员函数哦&#xff0c;包括构造函数&#xff0c;析构函数&#xff0c;拷贝构造函数&#xff0c;运算符重载函数&#xff0c;const成员函数&#xff0c;那么正篇开始。 文章目…

【OpenCV】OpenCV 4.9.0 正式发布

​ 开源计算机视觉库 OpenCV 4.9.0 已于2023年12月29日正式发布。 此次发布有DNN模块对ONNX Attention、Einsum等层的支持、新的fastGEMM实现、transformers的实验性支持等诸多亮点。 OpenCV 4.9.0 更新内容&#xff1a; &#xff08;来自OpenCV中国团队以及中国社区的贡献…