我们写一个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)
整理流程:
- 项目的启动类Application在指定Java环境下开启,并分配一个pid。
- 配置文件 Tomcat初始化,绑定端口
- 开启服务(Tomcat) 开启Servlet引擎 [Apache Tomcat/9.0.58]
- 初始化Spring嵌入式WebApplicationContext 根WebApplicationContext
- 初始化完成(ServletWebServerApplicationContext) Servlet的Listener逻辑
- 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流程:
- DispatcherServlet将所有HandlerMapping和HandlerAdapter加载进来。
- 有Http请求进来,Servlet将数据其封装到HttpServletRequest类,执行doService
- doService处理完一些信息后,分发给其他模块,也就是调用doDispatch
- getHandler方法分析request,得到一条handler处理器执行链
- 通过handler处理器执行链来getHandlerAdapter,得到支持该handler的适配器Adapter(加入handler是Controller注解开发的,那么将会得到SimpleControllerHandlerAdapter)
- 通过Adapter来handle,真正处理一些请求,得到ModelAndView(View只是viewName)
- 通过ViewResolver将viewName解析到真正的View
- render方法将view渲染(结合model)

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

被折叠的 条评论
为什么被折叠?



