0%

DispatcherServlet

DispatcherServlet

介绍

DispatcherServlet是一个Servlet,在springmvc中被称为前端控制器,根据请求的路径、类型等进行请求的分发。

分析

DispatcherServlet类图

DispatcherServlet类图.png

​ 上图中的红色部分为Servlet接口,在Springmvc中实现并扩展了该Servlet接口。在我最早学javaweb开发时,并不是上来就使用各种MVC框架,而是从编写简单的Servlet开始,就是继承HttpServlet后,重写它的service方法。而Springmvc的设计同理,它也继承了HttpServlet并重写它的service方法,同时配置该servlet拦截所有的请求,从而实现了由DispatcherServlet来分发所有的请求。

1. 初始化

​ 因为DispatcherServlet实际上是一个Servlet,所以当配置它生效时它同时也就遵循Servlet的生命周期。在Servlet的生命周期有三个阶段:

  • init():初始化请求
  • service():获取到请求后的业务处理以及跳转
  • distory():请求处理完成之后的销毁

所以当tomcat容器(servlet容器)启动时,会触发servlet的初始化方法。HttpServletBean实现了该方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Override
public final void init() throws ServletException {

// Set bean properties from init parameters.
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}catch (BeansException ex) {
if (logger.isErrorEnabled()) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
}
throw ex;
}
}

// 实际的处理在这, 由子类FrameworkServlet实现
// Let subclasses do whatever initialization they like.
initServletBean();
}

FrameworkServletinitServletBean方法,真正核心的地方在于initWebApplicationContext()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
protected final void initServletBean() throws ServletException {
getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
if (logger.isInfoEnabled()) {
logger.info("Initializing Servlet '" + getServletName() + "'");
}
long startTime = System.currentTimeMillis();

try {
// 初始化web容器上下文
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
}catch (ServletException | RuntimeException ex) {
logger.error("Context initialization failed", ex);
throw ex;
}

if (logger.isDebugEnabled()) {
String value = this.enableLoggingRequestDetails ?
"shown which may lead to unsafe logging of potentially sensitive data" :
"masked to prevent unsafe logging of potentially sensitive data";
logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
"': request parameters and headers will be " + value);
}

if (logger.isInfoEnabled()) {
logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
}
}

protected WebApplicationContext initWebApplicationContext() {
// 1.获取由ContextLoaderListener初始化并注册在ServletContext中的根上下文,记为rootContext
WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
// 2.如果webApplicationContext已经不为空,表示这个Servlet类是通过编程式注册到容器中的(Servlet 3.0+中的ServletContext.addServlet() ),
// 上下文也由编程式传入。若这个传入的上下文还没被初始化,将rootContext上下文设置为它的父上下文,然后将其初始化,否则直接使用。
if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent -> set
// the root application context (if any; may be null) as the parent
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
// 3.通过wac变量的引用是否为null,判断第2步中是否已经完成上下文的设置(即上下文是否已经用编程式方式传入),如果wac==null成立,说明该Servlet不是
// 由编程式注册到容器中的。此时以contextAttribute属性的值为键,在ServletContext中查找上下文,查找得到,说明上下文已经以别的方式初始化并注册在contextAttribute下,直接使用。
if (wac == null) {
// No context instance was injected at construction time -> see if one
// has been registered in the servlet context. If one exists, it is assumed
// that the parent context (if any) has already been set and that the
// user has performed any initialization such as setting the context id
wac = findWebApplicationContext();
}
// 4.检查wac变量的引用是否为null,如果wac==null成立,说明2、3两步中的上下文初始化策略都没成功,此时调用createWebApplicationContext(rootContext),建立一个全新的以rootContext
// 为父上下文的上下文,作为SpringMVC配置元素的容器上下文。大多数情况下我们所使用的上下文,就是这个新建的上下文。
if (wac == null) {
// No context instance is defined for this servlet -> create a local one
// 创建成功之后会调用spring的refresh方法
wac = createWebApplicationContext(rootContext);
}
// 5.以上三种初始化上下文的策略,都会回调onRefresh(ApplicationContext context)方法(回调的方式根据不同策略有不同),onRefresh方法在DispatcherServlet类中被覆写,
// 以上面得到的上下文为依托,完成SpringMVC中默认实现类的初始化。
if (!this.refreshEventReceived) {
// Either the context is not a ConfigurableApplicationContext with refresh
// support or the context injected at construction time had already been
// refreshed -> trigger initial onRefresh manually here.
synchronized (this.onRefreshMonitor) {
// onRefresh方法在DispatcherServlet类中被覆写,所以从这里可以知道先创建spring容器, 在springBean都创建成功之后, 再通过onRefresh方法进行DispatcherServlet的初始化。
onRefresh(wac);
}
}

// 6.最后,将这个上下文发布到ServletContext中,也就是将上下文以一个和Servlet类在web.xml中注册名字有关的值为键,设置为ServletContext的一个属性。你可以通过改变
// publishContext的值来决定是否发布到ServletContext中,默认为true。
if (this.publishContext) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}

通过onRefresh(wac)方法,调用到子类DispatcherServlet具体的初始化方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
* 初始化DispatcherServlet策略
*
* This implementation calls {@link #initStrategies}.
*/
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}

/**
* 初始化DispatcherServlet策略(九大组件)
*
* Initialize the strategy objects that this servlet uses.
* <p>May be overridden in subclasses in order to initialize further strategy objects.
*/
protected void initStrategies(ApplicationContext context) {
// 上传组件处理器
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
// 处理器映射器
initHandlerMappings(context);
// 处理器适配器
initHandlerAdapters(context);
// 处理器异常整理器
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
// 视图处理器
initViewResolvers(context);
initFlashMapManager(context);
}

2. 分发逻辑

springmvc流程图.png

  • 当客户端发起请求,请求进入DispatcherServlet,由它来进行处理器Handler的查找。
  • 首先根据请求信息HttpServletRequest,遍历已注册的所有handlerMappings。返回的结构为HandlerExecutionChain,是包含了具体的处理器handler和拦截器interceptor的结构。
  • 获取到HandlerExcecutionChain后,根据具体的处理器,遍历所有的handlerAdapters,返回支持的HandlerAdapter
  • 在获取到HandlerAdapter之后,执行具体的handler之前,会先遍历执行HandlerExecutionChain中的拦截器的前置拦截方法preHandle(),**若出现某个拦截器的前置方法执行后方法false,则直接从当前节点开始往前执行afterCompletion()**,执行完毕后直接终止请求。
  • 根据HandlerAdapter处理器适配器执行具体的处理器Handler逻辑。
  • 在执行完成具体的handler之后,会遍历执行HandlerExecutionChain中拦截器的postHandle方法。
  • Handler执行完毕之后会返回ModeAndView
  • 执行正常的情况下,在渲染模板后,请求返回前,会遍历执行HandlerExecutionChain中拦截器的afterCompletion方法。

3.设置springmvc容器

​ 根据类图可知DispatcherServlet也是实现了ApplicationContextAware接口,而xxxAware接口,是在springbean初始化时的一个postProcessBeforeInitialization扩展点,由类ApplicationContextAwareProcessor去执行具体的逻辑,简单来说就是将ApplicationContext通过setApplicationContext的方法传递给正在初始化的这个bean

​ 在springboot中就是通过这个方法,将spring容器(applicationContext)传给DispatcherServlet,待到真正去执行DispatcherServlet的初始化方法时this.applicationContext就不为空,所以在initWebApplicationContext()方法中,就走第一个if的逻辑,这也是springboot最终不会产生父子容器的原因。

-------------本文结束感谢您的阅读-------------