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处理程序处理的Web请求,并在这些处理程序返回的模型属性被传递到视图之前,对它们进行一些操作。doDispatch分别会调用applyPreHandle和applyPostHandle函数进行前置和后置处理,源码如下:

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

	/**
	 * 应用已注册拦截器的postHandle方法。
	 */
	void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
			throws Exception {
			
		for (int i = this.interceptorList.size() - 1; i >= 0; i--) {
			HandlerInterceptor interceptor = this.interceptorList.get(i);
			interceptor.postHandle(request, response, this.handler, mv);
		}
	}

 SpringMVC允许你通过处理拦截Web请求,进行前置处理和后置处理。处理逻辑是在Spring的Web应用程序上下文中配置的,因此它们可以可用各种容器特性,并引用容器中声明的任何bean。处理拦截是针对特殊的处理程序映射进行注册的,因此它只拦截通过这些处理程序映射的请求。每个处理拦截都必须实现HandlerInterceptor接口,它包含三个需要你实现的回调方法:preHandle、postHandle和afterCompletion。preHandle和postHandle分别是在处理程序处理请求之前和之后调用的,如果preHandle函数返回false,则处理程序会不会处理Web请求了,鉴于此,可以用于在preHandle函数中对请求进行过滤操作,例如账户认证过滤。postHandle方法还允许访问返回的ModelAndView对象,因此可以在它里面操作数据模型。afterCompletion方法是在所有的请求处理完成之后被调用的,以下是HandlerInterceptor简单实现:

/**
 * 通过HttpServletRequest的isUserInRole方法检查当前用户的角色授权的拦截器。
 *
 * @author Juergen Hoeller
 * @since 20.06.2003
 * @see jakarta.servlet.http.HttpServletRequest#isUserInRole
 */
public class UserRoleAuthorizationInterceptor implements HandlerInterceptor {

	@Nullable
	private String[] authorizedRoles;


	/**
	 * 设置此拦截器应视为授权的角色数组。
	 * @param authorizedRoles 角色名称数组
	 */
	public final void setAuthorizedRoles(String... authorizedRoles) {
		this.authorizedRoles = authorizedRoles;
	}


	@Override
	public final boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws ServletException, IOException {

		if (this.authorizedRoles != null) {
			for (String role : this.authorizedRoles) {
				if (request.isUserInRole(role)) {
					return true;
				}
			}
		}
		handleNotAuthorized(request, response, handler);
		return false;
	}

	/**
	 * 处理根据此拦截器未授权的请求。
	 * 默认实现发送HTTP状态码403("禁止")。
	 * <p>可以重写此方法以写入自定义消息,转发或重定向到错误页面或登录页面,或抛出ServletException。
	 * @param request 当前HTTP请求
	 * @param response 当前HTTP响应
	 * @param handler 选择执行的处理器,用于类型和/或实例评估
	 * @throws jakarta.servlet.ServletException 如有内部错误
	 * @throws java.io.IOException 在写入响应时发生I/O错误
	 */
	protected void handleNotAuthorized(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws ServletException, IOException {

		response.sendError(HttpServletResponse.SC_FORBIDDEN);
	}
}

在这个拦截器的preHandle方法中,对请求进行鉴权,如果鉴权通过则继续处理Web请求,否则就会返回请求被禁止的错误信息。

逻辑处理

对于逻辑处理其实是通过适配器中转调用Handler并返回视图的,源码如下:

mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

同样,还是以引导实例为基础进行处理逻辑分析,之前分析过,对于普通的Web请求,Spring默认使用SimpleControllerHandler-Adapter类进行处理,SimpleControllerHandlerAdapter类的源码如下:

/**
 * 简单控制器处理器适配器类
 */
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 ModelAndView对象
     * @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 最后修改时间,单位为毫秒
     */
    @Override
    @SuppressWarnings("deprecation")
    public long getLastModified(HttpServletRequest request, Object handler) {
        if (handler instanceof LastModified lastModified) {
            return lastModified.getLastModified(request);
        }
        return -1L;
    }

}

在上面的handle方法中直接调用org.springframework.web.servlet.mvc.AbstractController类的handleRequest函数,handleRequest函数的源码如下:

	/**
	 * 处理HTTP请求的方法
	 *
	 * @param request HTTP请求对象
	 * @param response HTTP响应对象
	 * @return 处理结果,可能为null
	 * @throws Exception 异常情况
	 */
	@Override
	@Nullable
	public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)
			throws Exception {

		if (HttpMethod.OPTIONS.matches(request.getMethod())) {
			response.setHeader(HttpHeaders.ALLOW, getAllowHeader());
			return null;
		}

		// 将请求委托给WebContentGenerator进行检查和准备
		checkRequest(request);
		prepareResponse(response);

		// 如果需要,使用同步块执行handleRequestInternal
		if (this.synchronizeOnSession) {
			HttpSession session = request.getSession(false);
			if (session != null) {
				Object mutex = WebUtils.getSessionMutex(session);
				synchronized (mutex) {
					return handleRequestInternal(request, response);
				}
			}
		}

        // 调用用户逻辑
		return handleRequestInternal(request, response);
	}

	/**
	 * 模板方法。子类必须实现此方法。
	 * 合同与{@code handleRequest}相同。
	 * @see #handleRequest
	 */
	@Nullable
	protected abstract ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
			throws Exception;

异常视图的处理

在Spring框架中,在处理Web请求时,如果处理过程出现异常错误,则会调用processHandlerException函数用于处理异常信息。

/**
 * 通过注册的HandlerExceptionResolvers确定错误的ModelAndView。
 *
 * @param request 当前HTTP请求
 * @param response 当前HTTP响应
 * @param handler 执行的处理器,如果在异常时未选择任何处理器,则为null(例如,如果multipart解析失败)
 * @param ex 在处理器执行期间抛出的异常
 * @return 要重定向到的相应的ModelAndView
 * @throws Exception 如果未找到错误的ModelAndView
 */
@Nullable
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
			@Nullable Object handler, Exception ex) throws Exception {

	// 成功和错误响应可能使用不同的内容类型
	request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
	// 如果响应尚未提交,则重置响应正文缓冲区,同时保留响应头。
	try {
		response.resetBuffer();
	}
	catch (IllegalStateException illegalStateException) {
		// 响应已经提交,仍然让异常处理程序处理它
	}

	// 检查已注册的HandlerExceptionResolvers...
	ModelAndView exMv = null;
	if (this.handlerExceptionResolvers != null) {
		for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
			exMv = resolver.resolveException(request, response, handler, ex);
			if (exMv != null) {
				break;
			}
		}
	}
	if (exMv != null) {
		if (exMv.isEmpty()) {
			request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
			return null;
		}
		// 我们仍然可能需要视图名称翻译,对于一个简单的错误模型...
		if (!exMv.hasView()) {
			String defaultViewName = getDefaultViewName(request);
			if (defaultViewName != null) {
				exMv.setViewName(defaultViewName);
			}
		}
		if (logger.isTraceEnabled()) {
			logger.trace("使用解析的错误视图: " + exMv,ex);
		}
		else if (logger.isDebugEnabled()) {
			logger.debug("使用解析的错误视图: " + exMv);
		}
		WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
		return exMv;
	}

	throw ex;
}

HandlerExceptionResolver是一个用于处理异常的解析器接口,它用于解析和处理在Spring应用程序中抛出的异常。实现该接口的类可以定义如何将异常转换为HTTP响应,从而提供自定义的异常处理机制。通过实现HandlerExceptionResolver接口,开发人员可以创建自定义的异常处理器来处理应用程序中的异常,并将其注册到Spring的应用程序上下文中。这样,当应用程序抛出异常时,Spring会自动调用相应的异常处理器来处理异常并生成相应的HTTP响应。

根据视图跳转页面

无论是一个系统还是一个站点,最重要的工作都是与用户进行交互,用户操作系统后无论下发的命令成功与否都要给用户一个反馈,以便于用户进行下一步的判断。所以在逻辑处理的最后一定会涉及页面调整的问题。

/**
 * 渲染给定的ModelAndView。
 * <p>这是处理请求的最后一个阶段,可能涉及通过名称解析视图。
 * @param mv 要渲染的ModelAndView
 * @param request 当前HTTP servlet请求
 * @param response 当前HTTP servlet响应
 * @throws ServletException 如果视图丢失或无法解析
 * @throws Exception 如果出现渲染视图的问题
 */
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
    // 确定请求的区域并将其应用于响应。
    Locale locale =
            (this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
    response.setLocale(locale);

    View view;
    String viewName = mv.getViewName();
    if (viewName != null) {
        // 我们需要解析视图名称。
        view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
        if (view == null) {
            throw new ServletException("无法在名称为 '" + mv.getViewName() +
                    "' 的 servlet 中解析名称为 '" + mv.getViewName() +
                    "' 的视图的名称'" + getServletName() + "'");
        }
    }
    else {
        // 没有必要查找:ModelAndView对象包含实际的View对象。
        view = mv.getView();
        if (view == null) {
            throw new ServletException("ModelAndView对象 [" + mv + "] 既不包含视图名称也不包含" +
                    "View对象在名称为 '" + getServletName() + "' 的servlet中");
        }
    }

    // 将渲染委托给View对象。
    if (logger.isTraceEnabled()) {
        logger.trace("正在渲染视图 [" + view + "] ");
    }
    try {
        if (mv.getStatus() != null) {
            request.setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, mv.getStatus());
            response.setStatus(mv.getStatus().value());
        }
        view.render(mv.getModelInternal(), request, response);
    }
    catch (Exception ex) {
        if (logger.isDebugEnabled()) {
            logger.debug("渲染视图 [" + view + "] 时出错", ex);
        }
        throw ex;
    }
}
1、解析视图名称

在上文中提到DispatcherServlet会根据ModelAndView选择合适的视图来进行渲染,而这一切的功能都是在resolveViewName函数中实现的,源码如下:

/**
 * 将给定的视图名称解析为要渲染的View对象。
 * <p>默认实现会询问该Dispatcher中的所有ViewResolver,可以重写以实现自定义解析策略,可能基于特定的模型属性或请求参数。
 * @param viewName 要解析的视图名称
 * @param model 要传递给视图的模型
 * @param locale 当前locale
 * @param request 当前HTTP servlet请求
 * @return 解析到的View对象,如果未找到则返回null
 * @throws Exception 如果无法解析视图(通常是在创建实际View对象时出现问题)
 * @see ViewResolver#resolveViewName
 */
@Nullable
protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
                               Locale locale, HttpServletRequest request) throws Exception {

    if (this.viewResolvers != null) {
        for (ViewResolver viewResolver : this.viewResolvers) {
            View view = viewResolver.resolveViewName(viewName, locale);
            if (view != null) {
                return view;
            }
        }
    }
    return null;
}

以org.springframework.web.servlet.view.AbstractCachingViewResolver为例分析ViewResolver逻辑的解析过程,其中函数resolveViewName源码如下:

/**
 * 重写方法:使用给定的视图名称和区域设置解析视图,并返回视图对象。
 * 如果缓存被禁用,则创建视图对象并返回;如果缓存被启用,则首先根据缓存键获取视图对象,
 * 如果未找到,则使用子类的createView方法创建视图对象,并将其同时放入视图访问缓存和视图创建缓存中;
 * 如果仍未找到,则返回UNRESOLVED_VIEW;如果找到,则根据缓存筛选器检查视图对象,
 * 如果通过筛选,则将其放入视图访问缓存和视图创建缓存中。
 * 最后返回经过处理的视图对象(如果视图对象为UNRESOLVED_VIEW,则返回null)。
 *
 * @param viewName 要解析的视图名称
 * @param locale 区域设置
 * @return 解析后的视图对象
 * @throws Exception 解析过程中发生的异常
 */
@Override
@Nullable
public View resolveViewName(String viewName, Locale locale) throws Exception {
    if (!isCache()) {
        return createView(viewName, locale);
    }
    else {
        Object cacheKey = getCacheKey(viewName, locale);
        View view = this.viewAccessCache.get(cacheKey);
        if (view == null) {
            synchronized (this.viewCreationCache) {
                view = this.viewCreationCache.get(cacheKey);
                if (view == null) {
                    // 请求子类创建视图对象。
                    view = createView(viewName, locale);
                    if (view == null && this.cacheUnresolved) {
                        view = UNRESOLVED_VIEW;
                    }
                    if (view != null && this.cacheFilter.filter(view, viewName, locale)) {
                        this.viewAccessCache.put(cacheKey, view);
                        this.viewCreationCache.put(cacheKey, view);
                    }
                }
            }
        }
        else {
            if (logger.isTraceEnabled()) {
                logger.trace(formatKey(cacheKey) + "从缓存中提供");
            }
        }
        return (view != UNRESOLVED_VIEW ? view : null);
    }
}

在上面的代码中,如果使用了缓存就在缓存中查询已经创建了的View,否则就重新创建一个新的View。在子类org.springframework.web.servlet.view.UrlBasedViewResolver类中重写了createView函数。源码如下:

	/**
	 * 重写以实现对 "redirect:" 前缀的检查。
	 * <p>由于子类的重写的 `loadView` 版本可能依赖于超类总是创建所需视图类的实例,因此在 `loadView` 中不可能实现此功能。
	 * @see #loadView
	 * @see #requiredViewClass
	 */
	@Override
	protected View createView(String viewName, Locale locale) throws Exception {
	    // 如果此解析器不应该处理给定的视图,则返回null,传递给链中的下一个解析器。
	    if (!canHandle(viewName, locale)) {
	        return null;
	    }

	    // 检查特殊 "redirect:" 前缀。
	    if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
	        String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
	        RedirectView view = new RedirectView(redirectUrl, isRedirectContextRelative(), isRedirectHttp10Compatible());
	        String[] hosts = getRedirectHosts();
	        if (hosts != null) {
	            view.setHosts(hosts);
	        }
	        return applyLifecycleMethods(REDIRECT_URL_PREFIX, view);
	    }

	    // 检查特殊 "forward:" 前缀。
	    if (viewName.startsWith(FORWARD_URL_PREFIX)) {
	        String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
	        InternalResourceView view = new InternalResourceView(forwardUrl);
	        return applyLifecycleMethods(FORWARD_URL_PREFIX, view);
	    }

	    // 否则回退到超类的实现:调用 loadView。
	    return super.createView(viewName, locale);
	}

在上述的代码中,会调用父类org.springframework.web.servlet.view.AbstractCachingViewResolver的createView函数,源码如下:

	/**
	 * 创建实际的视图对象。
	 * <p>默认实现委托给 {@link #loadView}。
	 * 可以在此处进行覆盖,以便在委托给子类提供的实际 {@code loadView} 实现之前,以特殊方式解析某些视图名称。
	 * @param viewName 要获取的视图的名称
	 * @param locale 要获取视图的区域设置
	 * @return View 实例,如果未找到则返回 {@code null}(可选的,以允许进行 ViewResolver 链接)
	 * @throws Exception 如果无法解析视图
	 * @see #loadView
	 */
	@Nullable
	protected View createView(String viewName, Locale locale) throws Exception {
	    return loadView(viewName, locale);
	}

	/**
	 * 通过调用 {@code buildView} 方法创建指定视图类的新实例。
	 * 应用以下 Spring 生命周期方法(由通用 Spring Bean 工厂支持):
	 * <ul>
	 *     <li>ApplicationContextAware 的 {@code setApplicationContext}</li>
	 *     <li>InitializingBean 的 {@code afterPropertiesSet}</li>
	 * </ul>
	 *
	 * @param viewName 要获取的视图的名称
	 * @param locale 当前的本地
	 * @return View 实例
	 * @throws Exception 如果无法解析视图
	 * @see #buildView(String)
	 * @see org.springframework.context.ApplicationContextAware#setApplicationContext
	 * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet
	 */
	@Override
	protected View loadView(String viewName, Locale locale) throws Exception {
	    AbstractUrlBasedView view = buildView(viewName);
	    View result = applyLifecycleMethods(viewName, view);
	    return (view.checkResource(locale) ? result : null);
	}

	/**
	 * 创建一个新的指定视图类的View实例并进行配置。
	 * 不执行任何查找预定义的视图实例的操作。
	 * <p>不需要在此处调用Spring生命周期方法,这些方法将由`loadView`方法在本方法返回后应用。
	 * <p>子类在使用`super.buildView(viewName)`设置进一步的属性之前通常会先调用此方法。`loadView`将在处理过程的最后应用Spring生命周期方法。
	 * @param viewName 要构建的视图的名称
	 * @return 视图实例
	 * @throws Exception 如果无法解析视图
	 * @see #loadView(String, java.util.Locale)
	 */
	protected AbstractUrlBasedView buildView(String viewName) throws Exception {
	    AbstractUrlBasedView view = instantiateView();
	    view.setUrl(getPrefix() + viewName + getSuffix());
	    view.setAttributesMap(getAttributesMap());

	    String contentType = getContentType();
	    if (contentType != null) {
	        view.setContentType(contentType);
	    }

	    String requestContextAttribute = getRequestContextAttribute();
	    if (requestContextAttribute != null) {
	        view.setRequestContextAttribute(requestContextAttribute);
	    }

	    Boolean exposePathVariables = getExposePathVariables();
	    if (exposePathVariables != null) {
	        view.setExposePathVariables(exposePathVariables);
	    }
	    Boolean exposeContextBeansAsAttributes = getExposeContextBeansAsAttributes();
	    if (exposeContextBeansAsAttributes != null) {
	        view.setExposeContextBeansAsAttributes(exposeContextBeansAsAttributes);
	    }
	    String[] exposedContextBeanNames = getExposedContextBeanNames();
	    if (exposedContextBeanNames != null) {
	        view.setExposedContextBeanNames(exposedContextBeanNames);
	    }

	    return view;
	}
2、页面跳转

当通过viewName解析到对应的View后,就可以进一步地处理跳转逻辑了。

	/**
	 * 根据指定的模型准备视图,将其与静态属性合并,并在必要时添加一个RequestContext属性,
	 * 然后将实际的渲染工作委托给renderMergedOutputModel方法。
	 * @see #renderMergedOutputModel
	 */
	@Override
	public void render(@Nullable Map<String, ?> model, HttpServletRequest request,
			HttpServletResponse response) throws Exception {

		if (logger.isDebugEnabled()) {
			logger.debug("视图 " + formatViewName() +
					", 模型 " + (model != null ? model : Collections.emptyMap()) +
					(this.staticAttributes.isEmpty() ? "" : ", 静态属性 " + this.staticAttributes));
		}

		// 创建合并后的输出模型
		Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
		prepareResponse(request, response);
		// 渲染合并后的输出模型
		renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
	}

	/**
	 * 创建一个包含动态值和静态属性的组合输出Map(永远不会为null)。
	 * 动态值优先于静态属性。
	 */
	protected Map<String, Object> createMergedOutputModel(@Nullable Map<String, ?> model,
			HttpServletRequest request, HttpServletResponse response) {

		@SuppressWarnings("unchecked")
		Map<String, Object> pathVars = (this.exposePathVariables ?
				(Map<String, Object>) request.getAttribute(View.PATH_VARIABLES) : null);

		// 整合静态和动态模型属性。
		int size = this.staticAttributes.size();
		size += (model != null ? model.size() : 0);
		size += (pathVars != null ? pathVars.size() : 0);

		Map<String, Object> mergedModel = CollectionUtils.newLinkedHashMap(size);
		mergedModel.putAll(this.staticAttributes);
		if (pathVars != null) {
			mergedModel.putAll(pathVars);
		}
		if (model != null) {
			mergedModel.putAll(model);
		}

		// 是否暴露RequestContext?
		if (this.requestContextAttribute != null) {
			mergedModel.put(this.requestContextAttribute, createRequestContext(request, response, mergedModel));
		}

		return mergedModel;
	}

/**
 * 根据指定的模型渲染内部资源。
 * 包括将模型作为请求属性设置。
 */
@Override
protected void renderMergedOutputModel(
		Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {

	// 将模型对象作为请求属性暴露。
	exposeModelAsRequestAttributes(model, request);

	// 如果有帮助程序,请将其作为请求属性暴露。
	exposeHelpers(request);

	// 准备渲染的路径。
	String dispatcherPath = prepareForRendering(request, response);

	// 获取用于目标资源(通常为JSP)的RequestDispatcher。
	RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
	if (rd == null) {
		throw new ServletException("无法获取RequestDispatcher来访问[" + getUrl() +
				"]:请确保您的Web应用程序存档中存在对应的文件!");
	}

	// 如果已包含或响应已提交,则执行include,否则执行forward。
	if (useInclude(request, response)) {
		response.setContentType(getContentType());
		if (logger.isDebugEnabled()) {
			logger.debug("包括[" + getUrl() + "]");
		}
		rd.include(request, response);
	}

	else {
		// 注意:转发的资源应该自己确定内容类型。
		if (logger.isDebugEnabled()) {
			logger.debug("转发到[" + getUrl() + "]");
		}
		rd.forward(request, response);
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值