SpringBoot中SpringMVC的DispatcherServlet工作流程?服务发布?源码解析?Servlet监听?Listener?注释翻译?代码详解?与Controller关系?

本文详细介绍了Servlet的Listener监听器如何在项目启动和关闭时初始化和销毁,以及如何通过ServletContextEvent传递事件。通过StartupListener示例和WebConfig配置,展示了如何在Spring Boot应用中集成和使用Listener。

我们写一个Listener去监听

什么是Listener?
Listener 即监听器,是servlet 的监听器。随web应用的启动而启动,只初始化一次,随web应用的停止而销毁。
当被监视的对象发生情况时,立即采取相应的行动(观察者模式)。
主要作用是做一些初始化的内容添加工作、设置一些基本的内容(比如一些参数或者是一些固定的对象)。
通过监听器,可以自动激发一些操作。比如:监听在线用户数量等等。

我们先实操:

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
import java.util.Arrays;

@WebListener
public class StartupListener implements ServletContextListener {

    /**
     * 监听项目启动,进行初始化
     *
     * @param sce ServletContextEvent对象
     */
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        // TODO Auto-generated method stub
        System.out.println("StartupListener init start..");
        ServletContext context = sce.getServletContext();
        System.out.println(Arrays.toString(context.getServletRegistrations().keySet().toArray()));
        System.out.println("StartupListener init end..");
    }

    /**
     * 监听项目终止,进行销毁
     *
     * @param sce ServletContextEvent对象
     */
    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        // TODO Auto-generated method stub
    }
}

同时写一个配置类,将这个Listener组件(Java三大组件为Servlet、Listener、Filter)添加进来。

import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
@ServletComponentScan(basePackages = {
        "com.micah.demo.listener",
})
public class WebConfig implements WebMvcConfigurer {
}

这是EventListener及其父类EventListener源码

package javax.servlet;

import java.util.EventListener;

/**
 * Implementations of this interface receive notifications about changes to the
 * servlet context of the web application they are part of. To receive
 * notification events, the implementation class must be configured in the
 * deployment descriptor for the web application.
 *
 * @see ServletContextEvent
 *
 * @since Servlet 2.3
 */
public interface ServletContextListener extends EventListener {

    /**
     ** Notification that the web application initialization process is starting.
     * All ServletContextListeners are notified of context initialization before
     * any filter or servlet in the web application is initialized.
     * The default implementation is a NO-OP.
     * @param sce Information about the ServletContext that was initialized
     */
    public default void contextInitialized(ServletContextEvent sce) {
    }

    /**
     ** Notification that the servlet context is about to be shut down. All
     * servlets and filters have been destroyed before any
     * ServletContextListeners are notified of context destruction.
     * The default implementation is a NO-OP.
     * @param sce Information about the ServletContext that was destroyed
     */
    public default void contextDestroyed(ServletContextEvent sce) {
    }
}

意思就是说当servlet context发生变化,那么就会接收到通知。这个接口实现类要进行配置。
contextInitialized方法:会在web应用初始化的时候发送一次通知,所有的ServletContextListeners都会在filter和servlet初始化之前就通知。
contextDestroyed方法:会在servlet context即将关闭的时候通知。在任何servletcontextlistener被这样通知之前,所有的servlet和filter已经被销毁。

package java.util;

/**
 * A tagging interface that all event listener interfaces must extend.
 * @since JDK1.1
 */
public interface EventListener {
}

这是一个所有listener接口的父类!

那么事件怎么来的呢?
看看ServletContextEvent类(也就是刚刚传入Listener方法的参数)及其父类EventObject:

package javax.servlet;

/**
 * This is the event class for notifications about changes to the servlet
 * context of a web application.
 *
 * @see ServletContextListener
 *
 * @since Servlet 2.3
 */
public class ServletContextEvent extends java.util.EventObject {

    private static final long serialVersionUID = 1L;

    /**
     * Construct a ServletContextEvent from the given context.
     *
     * @param source
     *            - the ServletContext that is sending the event.
     */
    public ServletContextEvent(ServletContext source) {
        super(source);
    }

    /**
     * Return the ServletContext that changed.
     *
     * @return the ServletContext that sent the event.
     */
    public ServletContext getServletContext() {
        return (ServletContext) super.getSource();
    }
}

父类

package java.util;

/**
 * <p>
 * The root class from which all event state objects shall be derived.
 * <p>
 * All Events are constructed with a reference to the object, the "source",
 * that is logically deemed to be the object upon which the Event in question
 * initially occurred upon.
 *
 * @since JDK1.1
 */

public class EventObject implements java.io.Serializable {

    private static final long serialVersionUID = 5516075349620653480L;

    /**
     * The object on which the Event initially occurred.
     */
    protected transient Object  source;

    /**
     * Constructs a prototypical Event.
     *
     * @param    source    The object on which the Event initially occurred.
     * @exception  IllegalArgumentException  if source is null.
     */
    public EventObject(Object source) {
        if (source == null)
            throw new IllegalArgumentException("null source");

        this.source = source;
    }

    /**
     * The object on which the Event initially occurred.
     *
     * @return   The object on which the Event initially occurred.
     */
    public Object getSource() {
        return source;
    }

    /**
     * Returns a String representation of this EventObject.
     *
     * @return  A a String representation of this EventObject.
     */
    public String toString() {
        return getClass().getName() + "[source=" + source + "]";
    }
}

可见,事件(Event)有事件的源,也就是那个source,它在ServletContextEvent就被描述成了ServletContext,也就是事件源就是上下文(的变化信息)。
所以我们根据接口进行编程(我们放心的根据它描述的标准来,后续进行测试即可)
所以就有我们之前写的那个例子,我们其实实现了这个监听器接口之后,添加到应用的组件库,那么就实现了监听的功能(因为SpringBoot会检测注解的,这个我们注解开发多了就会有感受)
这时候我们测试一下,开启SpringBoot应用,结果如下:

StartupListener init start..
[dispatcherServlet]
StartupListener init end..

怎么回事,只有一个Servlet?我们看一下SpringMVC的原理:
在这里插入图片描述
这个DispatcherServlet起到了中心枢纽的作用。
传统的JavaWeb,我们在Tomcat容器中添加了许多的Servlet,并且添加映射关系,才形成了一个完整的服务,但是SpringMVC打破了这种常规,只用了一个Servlet,而用HandlerMapping去根据请求路径映射到Controller来处理请求,得到ModelAndView,也就是模型和视图,可以近似说就是数据和页面。而View也要去通过ViewResolver(视图解析器)找到响应的资源,然后Model+View(解析后)就是最后的结果。
在Restful编程中,因为都是直接传JSON,这个View也就没有用上了。

那我们可以自己添加Servlet吗?可以参考下面的例子:

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/resource")
public class ResourceServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
        response.getWriter().print("ResourceServlet do get...");
        System.out.println("ResourceServlet do get...");
    }
}

参考Listener的添加流程,我们也可以把这个Servlet添加进来,毕竟同属JavaWeb三大组件。
访问url:http://localhost:${port}/resource

我们来看看整个SpringBoot启动流程


  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.6.4)

2022-03-01 11:59:58.948  INFO 22344 --- [           main] c.q.j.DemoApplication     : Starting JavaTamperResistantApplication using Java 1.8.0_321 on A00000-PC with PID 22344 (D:\IdeaProjects\demo\target\classes started by wuzexin in D:\IdeaProjects\demo)
2022-03-01 11:59:58.951  INFO 22344 --- [           main] c.q.j.DemoApplication     : No active profile set, falling back to 1 default profile: "default"
2022-03-01 11:59:59.690  INFO 22344 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8084 (http)
2022-03-01 11:59:59.697  INFO 22344 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2022-03-01 11:59:59.697  INFO 22344 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.58]
2022-03-01 11:59:59.782  INFO 22344 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2022-03-01 11:59:59.782  INFO 22344 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 785 ms
StartupListener init start..
[com.micah.demo.servlet.ResourceServlet, dispatcherServlet]
StartupListener init end..
2022-03-01 12:00:00.082  INFO 22344 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8084 (http) with context path ''
2022-03-01 12:00:00.089  INFO 22344 --- [           main] c.q.j.DemoApplication     : Started DemoApplication in 1.488 seconds (JVM running for 2.275)

整理流程:

  1. 项目的启动类Application在指定Java环境下开启,并分配一个pid。
  2. 配置文件 Tomcat初始化,绑定端口
  3. 开启服务(Tomcat) 开启Servlet引擎 [Apache Tomcat/9.0.58]
  4. 初始化Spring嵌入式WebApplicationContext 根WebApplicationContext
  5. 初始化完成(ServletWebServerApplicationContext) Servlet的Listener逻辑
  6. Tomcat在端口8084 (http) 开启服务 总结应用开启时长

以上只代表此例子,仅供参考。

我们知道这个DispatcherServlet非常重要,那么接下来我们来看看它的原理
首先看

# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.

org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver

org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver

org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
	org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\
	org.springframework.web.servlet.function.support.RouterFunctionMapping

org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
	org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
	org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\
	org.springframework.web.servlet.function.support.HandlerFunctionAdapter


org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
	org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
	org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator

org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver

org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager

翻译:
DispatcherServlet策略接口的默认实现类。
当在DispatcherServlet上下文中没有找到匹配的bean时,用作回退。
不适合应用开发者定制。

看一下DispatcherServlet的注释

/**
 * Central dispatcher for HTTP request handlers/controllers, e.g. for web UI controllers
 * or HTTP-based remote service exporters. Dispatches to registered handlers for processing
 * a web request, providing convenient mapping and exception handling facilities.
 *
 * <p>This servlet is very flexible: It can be used with just about any workflow, with the
 * installation of the appropriate adapter classes. It offers the following functionality
 * that distinguishes it from other request-driven web MVC frameworks:
 *
 * <ul>
 * <li>It is based around a JavaBeans configuration mechanism.
 *
 * <li>It can use any {@link HandlerMapping} implementation - pre-built or provided as part
 * of an application - to control the routing of requests to handler objects. Default is
 * {@link org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping} and
 * {@link org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping}.
 * HandlerMapping objects can be defined as beans in the servlet's application context,
 * implementing the HandlerMapping interface, overriding the default HandlerMapping if
 * present. HandlerMappings can be given any bean name (they are tested by type).
 *
 * <li>It can use any {@link HandlerAdapter}; this allows for using any handler interface.
 * Default adapters are {@link org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter},
 * {@link org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter}, for Spring's
 * {@link org.springframework.web.HttpRequestHandler} and
 * {@link org.springframework.web.servlet.mvc.Controller} interfaces, respectively. A default
 * {@link org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter}
 * will be registered as well. HandlerAdapter objects can be added as beans in the
 * application context, overriding the default HandlerAdapters. Like HandlerMappings,
 * HandlerAdapters can be given any bean name (they are tested by type).
 *
 * <li>The dispatcher's exception resolution strategy can be specified via a
 * {@link HandlerExceptionResolver}, for example mapping certain exceptions to error pages.
 * Default are
 * {@link org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver},
 * {@link org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver}, and
 * {@link org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver}.
 * These HandlerExceptionResolvers can be overridden through the application context.
 * HandlerExceptionResolver can be given any bean name (they are tested by type).
 *
 * <li>Its view resolution strategy can be specified via a {@link ViewResolver}
 * implementation, resolving symbolic view names into View objects. Default is
 * {@link org.springframework.web.servlet.view.InternalResourceViewResolver}.
 * ViewResolver objects can be added as beans in the application context, overriding the
 * default ViewResolver. ViewResolvers can be given any bean name (they are tested by type).
 *
 * <li>If a {@link View} or view name is not supplied by the user, then the configured
 * {@link RequestToViewNameTranslator} will translate the current request into a view name.
 * The corresponding bean name is "viewNameTranslator"; the default is
 * {@link org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator}.
 *
 * <li>The dispatcher's strategy for resolving multipart requests is determined by a
 * {@link org.springframework.web.multipart.MultipartResolver} implementation.
 * Implementations for Apache Commons FileUpload and Servlet 3 are included; the typical
 * choice is {@link org.springframework.web.multipart.commons.CommonsMultipartResolver}.
 * The MultipartResolver bean name is "multipartResolver"; default is none.
 *
 * <li>Its locale resolution strategy is determined by a {@link LocaleResolver}.
 * Out-of-the-box implementations work via HTTP accept header, cookie, or session.
 * The LocaleResolver bean name is "localeResolver"; default is
 * {@link org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver}.
 *
 * <li>Its theme resolution strategy is determined by a {@link ThemeResolver}.
 * Implementations for a fixed theme and for cookie and session storage are included.
 * The ThemeResolver bean name is "themeResolver"; default is
 * {@link org.springframework.web.servlet.theme.FixedThemeResolver}.
 * </ul>
 *
 * <p><b>NOTE: The {@code @RequestMapping} annotation will only be processed if a
 * corresponding {@code HandlerMapping} (for type-level annotations) and/or
 * {@code HandlerAdapter} (for method-level annotations) is present in the dispatcher.</b>
 * This is the case by default. However, if you are defining custom {@code HandlerMappings}
 * or {@code HandlerAdapters}, then you need to make sure that a corresponding custom
 * {@code RequestMappingHandlerMapping} and/or {@code RequestMappingHandlerAdapter}
 * is defined as well - provided that you intend to use {@code @RequestMapping}.
 *
 * <p><b>A web application can define any number of DispatcherServlets.</b>
 * Each servlet will operate in its own namespace, loading its own application context
 * with mappings, handlers, etc. Only the root application context as loaded by
 * {@link org.springframework.web.context.ContextLoaderListener}, if any, will be shared.
 *
 * <p>As of Spring 3.1, {@code DispatcherServlet} may now be injected with a web
 * application context, rather than creating its own internally. This is useful in Servlet
 * 3.0+ environments, which support programmatic registration of servlet instances.
 * See the {@link #DispatcherServlet(WebApplicationContext)} javadoc for details.
 *
 * @author Rod Johnson
 * @author Juergen Hoeller
 * @author Rob Harrop
 * @author Chris Beams
 * @author Rossen Stoyanchev
 * @author Sebastien Deleuze
 * @see org.springframework.web.HttpRequestHandler
 * @see org.springframework.web.servlet.mvc.Controller
 * @see org.springframework.web.context.ContextLoaderListener
 */

翻译:

HTTP请求处理程序/控制器的中央调度程序,例如web UI控制器或基于http的远程服务导出器。分派到已注册的处理程序进行处理一个web请求,提供方便的映射和异常处理设施。
这个servlet非常灵活:它可以用于任何工作流,以及安装适当的适配器类。它提供以下功能使其区别于其他请求驱动的web MVC框架:

它基于javabean配置机制。

它可以使用任何{@link HandlerMapping}实现,预构建的,或作为一部分提供的,用于控制请求到处理对象的路由。默认是{@link org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping}和
{@link org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping}。
HandlerMapping对象可以定义为servlet应用上下文中的bean,实现HandlerMapping接口,覆盖默认的HandlerMapping如果存在的话。HandlerMappings可以被赋予任何bean名称,它们按照类型被测试。

它可以使用任何{@link HandlerAdapter};这允许使用任何处理程序接口。
默认的适配器是{@link org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter},
{@link org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter},分别地提供给Spring的
{@link org.springframework.web.HttpRequestHandler},
{@link org.springframework.web.servlet.mvc.Controller}接口。一个默认的
{@link org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter}也将被注册。HandlerAdapter对象可以作为bean添加到应用程序上下文,覆盖默认的handleradapter。像HandlerMappings一样,
handleradapter也可以被赋予任何bean名称(它们是通过类型测试的)。

调度程序的异常解决策略可以被指定要经过一个
{@link HandlerExceptionResolver},例如将某些异常映射到错误页面。
默认是
{@link org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver},
{@link org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver},和
{@link org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver}。
这些HandlerExceptionResolvers可以通过应用上下文被覆盖。
HandlerExceptionResolver可以被赋予任何bean名称(它们是通过类型测试的)。

它的视图解析策略可以通过{@link ViewResolver}指定实现,解析符号视图名到视图对象。默认是
{@link org.springframework.web.servlet.view.InternalResourceViewResolver}。
ViewResolver对象可以作为bean添加到应用程序中,覆盖默认的ViewResolver。ViewResolvers可以被赋予任何bean名称(它们通过类型进行测试)。

如果用户没有提供{@link View}或视图名,则配置{@link RequestToViewNameTranslator}将当前请求转换为视图名。对应的bean名是“viewNameTranslator”;默认值是
{@link org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator}。

调度程序解决复合请求的策略是由一个
{@link org.springframework.web.multipart.MultipartResolver}的实现确定的。
Apache Commons FileUpload和Servlet 3的实现包括在内;典型的选择是
{@link org.springframework.web.multipart. commonmultipartresolver}。
MultipartResolver bean的名称是“MultipartResolver”;默认是没有的。

它的区域设置解析策略由{@link LocaleResolver}决定。开箱即用的实现工作通过HTTP接受头、cookie或session。
LocaleResolver bean的名字是“LocaleResolver”;默认是
{@link org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver}。

它的主题解决策略由{@link ThemeResolver}决定。实现的固定主题和cookie和session存储包括在内。
ThemeResolver bean的名字是“ThemeResolver”;默认是
{@link org.springframework.web.servlet.theme.FixedThemeResolver}。

注意:{@code @RequestMapping}注解只会在有一个相关的{@code HandlerMapping}(用于类型级别的注释)和/或
{@code HandlerAdapter}(用于方法级注释)在dispatcher中时才会被被处理。
这是默认情况。但是,如果你定义了自定义的{@code HandlerMappings}
或{@code HandlerAdapters},那么你需要确保一个相应的自定义
{@code RequestMappingHandlerMapping}和/或
{@code RequestMappingHandlerAdapter}也被定义,
如果你打算使用{@code @RequestMapping}的话。

一个web应用程序可以定义任意数量的DispatcherServlets。
每个servlet将在自己的命名空间中操作,加载自己的application context与mappings,handlers等。
只有加载根应用程序上下文
{@link org.springframework.web.context.ContextLoaderListener}(如果有的话)才将被共享。

从Spring 3.1开始,{@code DispatcherServlet}现在可以被web注入应用的上下文,而不是创建自己的内部。这在Servlet 3.0+ 环境中非常有用,支持有计划地注册servlet实例。
详见{@link #DispatcherServlet(WebApplicationContext)} javadoc。

详见:
org.springframework.web.HttpRequestHandler
org.springframework.web.servlet.mvc.Controller
org.springframework.web.context.ContextLoaderListener

继承关系:
在这里插入图片描述
继承链是DispatcherServlet->FrameworkServlet->HttpServletBean->HttpServlet->GenericServlet
Aware是策略模式的体现。
看看DispatcherServlet的非私有方法:
在这里插入图片描述
可以看到,虽然没有doGet等,但有一个doService方法(以下直接给出注释+翻译):

/**
	 * Exposes the DispatcherServlet-specific request attributes and delegates to {@link #doDispatch}
	 * for the actual dispatching.
	 * 将特定于dispatcherservlet的请求属性和委托暴露给{@link #doDispatch},以实现实际的调度。  
	 */
	@Override
	protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
		logRequest(request);

		// Keep a snapshot of the request attributes in case of an include,
		// to be able to restore the original attributes after the include.
		// 在include的情况下保存请求属性的快照
		// 这样就可以在include之后能够恢复原始属性。
		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));
				}
			}
		}

		// Make framework objects available to handlers and view objects.
		// 使框架对象对处理程序和视图对象可用
		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()) {
				// Restore the original attribute snapshot, in case of an include.
				if (attributesSnapshot != null) {
					restoreAttributesAfterInclude(request, attributesSnapshot);
				}
			}
			if (this.parseRequestPath) {
				ServletRequestPathUtils.setParsedRequestPath(previousRequestPath, request);
			}
		}
	}

可以看出是对请求做了一部分处理,然后交给doDispatch方法,我们继续追踪:

/**
	 * Process the actual dispatching to the handler.
	 * <p>The handler will be obtained by applying the servlet's HandlerMappings in order.
	 * The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
	 * to find the first that supports the handler class.
	 * <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
	 * themselves to decide which methods are acceptable.
	 * 
	 * 处理实际的分派给处理程序。 该处理程序将通过顺序应用servlet的HandlerMappings获得。
	 * HandlerAdapter将通过查询servlet中安装的HandlerAdapter来找到第一个支持处理程序类的HandlerAdapter来获得。 
	 * 所有HTTP方法都由此方法处理。 由handleradapter或处理程序本身决定哪些方法是可接受的。  
	 * 
	 * @param request current HTTP request
	 * @param response current HTTP response
	 * @throws Exception in case of any kind of processing failure
	 */
	@SuppressWarnings("deprecation")
	protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
		HttpServletRequest processedRequest = request;
		HandlerExecutionChain mappedHandler = null;
		boolean multipartRequestParsed = false;

		WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

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

			try {
				processedRequest = checkMultipart(request);
				multipartRequestParsed = (processedRequest != request);

				// Determine handler for the current request.
				mappedHandler = getHandler(processedRequest);
				if (mappedHandler == null) {
					noHandlerFound(processedRequest, response);
					return;
				}

				// Determine handler adapter for the current request.
				HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

				// Process last-modified header, if supported by the handler.
				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;
					}
				}

				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					return;
				}

				// Actually invoke the handler.
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

				if (asyncManager.isConcurrentHandlingStarted()) {
					return;
				}

				applyDefaultViewName(processedRequest, mv);
				mappedHandler.applyPostHandle(processedRequest, response, mv);
			}
			catch (Exception ex) {
				dispatchException = ex;
			}
			catch (Throwable err) {
				// As of 4.3, we're processing Errors thrown from handler methods as well,
				// making them available for @ExceptionHandler methods and other scenarios.
				dispatchException = new NestedServletException("Handler dispatch failed", err);
			}
			processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
		}
		catch (Exception ex) {
			triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
		}
		catch (Throwable err) {
			triggerAfterCompletion(processedRequest, response, mappedHandler,
					new NestedServletException("Handler processing failed", err));
		}
		finally {
			if (asyncManager.isConcurrentHandlingStarted()) {
				// Instead of postHandle and afterCompletion
				if (mappedHandler != null) {
					mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
				}
			}
			else {
				// Clean up any resources used by a multipart request.
				if (multipartRequestParsed) {
					cleanupMultipart(processedRequest);
				}
			}
		}
	}

可以看到,分发调度的时候,先看getHandler拿到HandleMapping,传给getHandlerAdapter来拿到适配器(这里叫做ha),
最后调用 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()) 是真正调用处理器。

追溯handle:

	/**
	 * Use the given handler to handle this request.
	 * The workflow that is required may vary widely.
	 * @param request current HTTP request
	 * @param response current HTTP response
	 * @param handler the handler to use. This object must have previously been passed
	 * to the {@code supports} method of this interface, which must have
	 * returned {@code true}.
	 * @return a ModelAndView object with the name of the view and the required
	 * model data, or {@code null} if the request has been handled directly
	 * @throws Exception in case of errors
	 */
	@Nullable
	ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;

由于HandlerAdapter有多种实现(因为有多种方案来添加网页请求处理器),我们挑SpringMVC经常使用的Controller注解开发的适配器SimpleControllerHandlerAdapter来看看其功能实现:

package org.springframework.web.servlet.mvc;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.lang.Nullable;
import org.springframework.web.servlet.HandlerAdapter;
import org.springframework.web.servlet.ModelAndView;

/**
 * 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 {

	@Override
	public boolean supports(Object handler) {
		return (handler instanceof Controller);
	}

	@Override
	@Nullable
	public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {

		return ((Controller) handler).handleRequest(request, response);
	}

	@Override
	@SuppressWarnings("deprecation")
	public long getLastModified(HttpServletRequest request, Object handler) {
		if (handler instanceof LastModified) {
			return ((LastModified) handler).getLastModified(request);
		}
		return -1L;
	}

}

可以看到直接将Object强转为Controller类型,然后调用handleRequest方法。
其实我们发现,从刚刚开始的流程里,一直是利用接口进行调用,我们从没有看见一个实现类来调用方法的,DispatcherServlet中几乎所有的变量的引用也都是接口,具体接口引用的对象是哪个实现类,我们得去看初始化部分,看看其中加入了什么。
就例如HandlerAdapter,我们一直看到它的存在,它就是一个标准,一个适配,提供的support方法用来判断这个适配器是否可以用来解决这个问题。
回想一下,我们在DispatcherServlet的doService->doDispatcher的时候,有getHandlerAdapter函数(那次调用得到了ha这个变量),我们看看getHandlerAdapter源码:

	/**
	 * Return the HandlerAdapter for this handler object.
	 * @param handler the handler object to find an adapter for
	 * @throws ServletException if no HandlerAdapter can be found for the handler. This is a fatal error.
	 */
	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");
	}

就是将所有的适配器都拿出来判断,如果支持,就直接返回,拿到一个能解决问题的就够了,后续也就不判断了。
当时的SimpleControllerHandlerAdapter被拿出来,执行 supports 的时候发现处理器 handler instanceof Controller,所以就使用了这个适配器来解决问题。
handler从哪里来?

	/**
	 * Return the HandlerExecutionChain for this request.
	 * <p>Tries all handler mappings in order.
	 * @param request current HTTP request
	 * @return the HandlerExecutionChain, or {@code null} if no handler could be found
	 */
	@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被拿出来,也是因为getHandler方法在众多的handlerMappings中,用 mapping.getHandler(request)找到了相应的handler。也就是将所有网络映射mapping拿出来,看看哪个是可以处理这个request的,而且得到的还是个执行链HandlerExecutionChain。
handlerMappings里的mapping什么时候添加进来的?

/**
	 * Initialize the HandlerMappings used by this class.
	 * <p>If no HandlerMapping beans are defined in the BeanFactory for this namespace,
	 * we default to BeanNameUrlHandlerMapping.
	 */
	private void initHandlerMappings(ApplicationContext context) {
		this.handlerMappings = null;

		if (this.detectAllHandlerMappings) {
			// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
			Map<String, HandlerMapping> matchingBeans =
					BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
			if (!matchingBeans.isEmpty()) {
				this.handlerMappings = new ArrayList<>(matchingBeans.values());
				// We keep HandlerMappings in sorted order.
				AnnotationAwareOrderComparator.sort(this.handlerMappings);
			}
		}
		else {
			try {
				HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
				this.handlerMappings = Collections.singletonList(hm);
			}
			catch (NoSuchBeanDefinitionException ex) {
				// Ignore, we'll add a default HandlerMapping later.
			}
		}

		// Ensure we have at least one HandlerMapping, by registering
		// a default HandlerMapping if no other mappings are found.
		if (this.handlerMappings == null) {
			this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
			if (logger.isTraceEnabled()) {
				logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
						"': using default strategies from DispatcherServlet.properties");
			}
		}

		for (HandlerMapping mapping : this.handlerMappings) {
			if (mapping.usesPathPatterns()) {
				this.parseRequestPath = true;
				break;
			}
		}
	}

可以明显看出,就是一个Bean工厂,去找到所有HandlerMapping.class(包含祖先),然后加载进来。
顺便瞄一眼工厂方法如何加载这些Mapping的:

    public static <T> Map<String, T> beansOfTypeIncludingAncestors(ListableBeanFactory lbf, Class<T> type, boolean includeNonSingletons, boolean allowEagerInit) throws BeansException {
        Assert.notNull(lbf, "ListableBeanFactory must not be null");
        Map<String, T> result = new LinkedHashMap(4);
        result.putAll(lbf.getBeansOfType(type, includeNonSingletons, allowEagerInit));
        if (lbf instanceof HierarchicalBeanFactory) {
            HierarchicalBeanFactory hbf = (HierarchicalBeanFactory)lbf;
            if (hbf.getParentBeanFactory() instanceof ListableBeanFactory) {
                Map<String, T> parentResult = beansOfTypeIncludingAncestors((ListableBeanFactory)hbf.getParentBeanFactory(), type, includeNonSingletons, allowEagerInit);
                parentResult.forEach((beanName, beanInstance) -> {
                    if (!result.containsKey(beanName) && !hbf.containsLocalBean(beanName)) {
                        result.put(beanName, beanInstance);
                    }

                });
            }
        }

        return result;
    }

可以看出是一个递归的调用,一直调用父BeanFactory完成对所有bean的加载,而且type还必须是刚刚传进来的HandlerMapping。

所以我们解决了SpringMVC的处理器handler(Controller)是如何被使用的问题。
后面handle方法返回的ModelAndView,也需要处理:
applyDefaultViewName(processedRequest, mv) 是进行视图名转换
mappedHandler.applyPostHandle(processedRequest, response, mv)是应用已注册的拦截器interceptors的postHandle方法。
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException)是处理处理程序选择和处理程序调用的结果,即一个ModelAndView或一个异常被解析到一个ModelAndView。

我们找到render(mv, request, response),我们判断,render是一个渲染视图的方法。

/**
	 * Render the given ModelAndView.
	 * <p>This is the last stage in handling a request. It may involve resolving the view by name.
	 * @param mv the ModelAndView to render
	 * @param request current HTTP servlet request
	 * @param response current HTTP servlet response
	 * @throws ServletException if view is missing or cannot be resolved
	 * @throws Exception if there's a problem rendering the view
	 */
	protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
		// Determine locale for request and apply it to the response.
		Locale locale =
				(this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
		response.setLocale(locale);

		View view;
		String viewName = mv.getViewName();
		if (viewName != null) {
			// We need to resolve the view name.
			view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
			if (view == null) {
				throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
						"' in servlet with name '" + getServletName() + "'");
			}
		}
		else {
			// No need to lookup: the ModelAndView object contains the actual View object.
			view = mv.getView();
			if (view == null) {
				throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
						"View object in servlet with name '" + getServletName() + "'");
			}
		}

		// Delegate to the View object for rendering.
		if (logger.isTraceEnabled()) {
			logger.trace("Rendering view [" + 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("Error rendering view [" + view + "]", ex);
			}
			throw ex;
		}
	}

view = resolveViewName(viewName, mv.getModelInternal(), locale, request)就是调用视图解析器来解析视图:

	/**
	 * Resolve the given view name into a View object (to be rendered).
	 * <p>The default implementations asks all ViewResolvers of this dispatcher.
	 * Can be overridden for custom resolution strategies, potentially based on
	 * specific model attributes or request parameters.
	 * @param viewName the name of the view to resolve
	 * @param model the model to be passed to the view
	 * @param locale the current locale
	 * @param request current HTTP servlet request
	 * @return the View object, or {@code null} if none found
	 * @throws Exception if the view cannot be resolved
	 * (typically in case of problems creating an actual View object)
	 * @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;
	}

这样viewName就变成了View

另一个比较重要的view.render(mv.getModelInternal(), request, response)。我们继续深究:

	/**
	 * Render the view given the specified model.
	 * <p>The first step will be preparing the request: In the JSP case, this would mean
	 * setting model objects as request attributes. The second step will be the actual
	 * rendering of the view, for example including the JSP via a RequestDispatcher.
	 * @param model a Map with name Strings as keys and corresponding model
	 * objects as values (Map can also be {@code null} in case of empty model)
	 * @param request current HTTP request
	 * @param response he HTTP response we are building
	 * @throws Exception if rendering failed
	 */
	void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)
			throws Exception;

顾名思义,就是渲染的意思。
其实现:
在这里插入图片描述
也就是说有多种渲染方案
我们看其中一个,AbstractView,它就有render渲染方法:

	/**
	 * Prepares the view given the specified model, merging it with static
	 * attributes and a RequestContext attribute, if necessary.
	 * Delegates to renderMergedOutputModel for the actual rendering.
	 * @see #renderMergedOutputModel
	 */
	@Override
	public void render(@Nullable Map<String, ?> model, HttpServletRequest request,
			HttpServletResponse response) throws Exception {

		if (logger.isDebugEnabled()) {
			logger.debug("View " + formatViewName() +
					", model " + (model != null ? model : Collections.emptyMap()) +
					(this.staticAttributes.isEmpty() ? "" : ", static attributes " + this.staticAttributes));
		}

		Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
		prepareResponse(request, response);
		renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
	}

renderMergedOutputModel方法是真正的渲染逻辑
又是一个抽象方法,所以我们又找到里边的实现:
在这里插入图片描述
我们看一个InternalResourceView,这是一个内部资源的视图,其renderMergedOutputModel方法中第一个就是exposeModelAsRequestAttributes方法,我们看看源码:

	/**
	 * Expose the model objects in the given map as request attributes.
	 * Names will be taken from the model Map.
	 * This method is suitable for all resources reachable by {@link javax.servlet.RequestDispatcher}.
	 * @param model a Map of model objects to expose
	 * @param request current HTTP request
	 */
	protected void exposeModelAsRequestAttributes(Map<String, Object> model,
			HttpServletRequest request) throws Exception {

		model.forEach((name, value) -> {
			if (value != null) {
				request.setAttribute(name, value);
			}
			else {
				request.removeAttribute(name);
			}
		});
	}

其实是调用了AbstractView的默认方法,将模型通过request域的方式暴露。
到这里,我们就知道了视图模型的解析的大概流程。

回想一下,是不是不记得刚刚看了什么了?

总计一下DispatcherServlet流程:

  1. DispatcherServlet将所有HandlerMapping和HandlerAdapter加载进来。
  2. 有Http请求进来,Servlet将数据其封装到HttpServletRequest类,执行doService
  3. doService处理完一些信息后,分发给其他模块,也就是调用doDispatch
  4. getHandler方法分析request,得到一条handler处理器执行链
  5. 通过handler处理器执行链来getHandlerAdapter,得到支持该handler的适配器Adapter(加入handler是Controller注解开发的,那么将会得到SimpleControllerHandlerAdapter)
  6. 通过Adapter来handle,真正处理一些请求,得到ModelAndView(View只是viewName)
  7. 通过ViewResolver将viewName解析到真正的View
  8. render方法将view渲染(结合model)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值