缺省配置Springboot Web应用运行中DispatchServlet的初始化

这篇博客详细分析了Springboot Web应用中DispatcherServlet的初始化过程,包括启动准备和运行时的初始化。文章深入源代码,探讨了DispatcherServlet如何进行配置、其初始化方法的实现,以及缺省策略组件的设定。同时,提到了Spring MVC的官方文档,并引用了相关参考资料以供深入学习。

概述

缺省配置Springboot Web应用中的SpringMVC DispatcherServlet,从整个应用开始启动到最终处于服务请求的状态,粗略地讲,可以分为两步 :

  1. 缺省配置Springboot Web应用启动过程中SpringMVC DispatcherServlet的准备

这一步相当于把DispatcherServlet安装部署到一个可以随时服务请求的web应用中去;

关于这一步的详细情况,可以参考文章
缺省配置Springboot Web应用启动中准备SpringMVC DispatcherServlet

  1. 缺省配置Springboot Web应用运行中SpringMVC DispatcherServlet的初始化

这一步指的是Servlet.init()方法的调用,本文主要分析这一过程。

源代码分析

HttpServletBeaninit方法

DispatcherServlet和父类的继承关系如下 :

DispatcherServlet -> FrameworkServlet -> HttpServletBean --> HttpServlet

这个继承关系里面,DispatcherServlet,FrameworkServlet,HttpServletBean都是Spring的类,位于以下包 :

org.springframework.web.servlet

HttpServletJava Servlet API,表示一个HTTP Servlet,是一个抽象类,位于包 :

javax.servlet.http

这里面Servlet的初始化方法 init() 实现在 HttpServletBean 中 :

	// 类 org.springframework.web.servlet.HttpServletBean 的方法
	@Override
	public final void init() throws ServletException {
		if (logger.isDebugEnabled()) {
			logger.debug("Initializing servlet '" + getServletName() + "'");
		}

		// Set bean properties from init parameters.
		// 从初始化参数 init paramters 中提取参数值设置bean属性
		// 完全缺省配置的Springboot Web应用,这里最终的pvs为空:pvs.isEmpty()==true
		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;
			}
		}

		// Let subclasses do whatever initialization they like.
		// initServletBean()是HttpServletBean定义的一个空方法,目的是让子类填充相应的初始化实现 
		initServletBean();

		if (logger.isDebugEnabled()) {
			logger.debug("Servlet '" + getServletName() + "' configured successfully");
		}
	}

FrameworkServletinitServletBean方法

从上面的分析可以看出,HttpServletBean.init()在做了一些简单的参数设置时候,具体的初始化工作交给了子类对方法initServletBean()的具体实现。而作为子类,FrameworkServlet 实现了方法initServletBean(),源代码如下 :

// 类 org.springframework.web.servlet.FrameworkServlet 的方法
/**
 * 对父类HttpServletBean所定义方法的覆盖实现, 在所有bean属性设置后被调用,
 * 用于创建servlet的WebApplicationContext实例
 */
@Override
protected final void initServletBean() throws ServletException {
	// 日志输出,控制台上看到的效果如下 :
	// Initializing Spring FrameworkServlet 'dispatcherServlet'
	// FrameworkServlet 'dispatcherServlet': initialization started
	getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
	if (this.logger.isInfoEnabled()) {
		this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
	}
	long startTime = System.currentTimeMillis();

	try {
		// 创建针对该Servlet(也就是DispatcherServlet)的Web ApplicationContext
		this.webApplicationContext = initWebApplicationContext();
		// initFrameworkServlet是FrameworkServlet提供的空方法,
		// 用于在所有属性已设置,并且Servlet WebApplicationContext加载后
		// 提供给子类一个机会执行一些它们需要的初始化工作
		initFrameworkServlet();
	}
	catch (ServletException ex) {
		this.logger.error("Context initialization failed", ex);
		throw ex;
	}
	catch (RuntimeException ex) {
		this.logger.error("Context initialization failed", ex);
		throw ex;
	}

	if (this.logger.isInfoEnabled()) {
		long elapsedTime = System.currentTimeMillis() - startTime;
		this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
				elapsedTime + " ms");
	}
}

/**
 * Initialize and publish the WebApplicationContext for this servlet.
 * Delegates to createWebApplicationContext for actual creation
 * of the context. Can be overridden in subclasses.
 * @return the WebApplicationContext instance
 * @see #FrameworkServlet(WebApplicationContext)
 * @see #setContextClass
 * @see #setContextConfigLocation
 */
protected WebApplicationContext initWebApplicationContext() {
	// 获取当前Servlet实例所在的Web应用的WebApplicationContext,称之为根上下文rootContext,
	// 该根上下文在创建成功后记录为servletContext属性
	// WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,
	// 这里通过工具类WebApplicationContextUtils把它取出来使用。
	//
	// 在完全采用缺省配置的Springboot Web应用中,它是一个如下类的实例
	// org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext
	WebApplicationContext rootContext =
			WebApplicationContextUtils.getWebApplicationContext(getServletContext());
	// 现在准备创建当前Servlet的WebApplicationContext了,将要把它记录为wac
	WebApplicationContext wac = null;

	if (this.webApplicationContext != null) {
		// 如果当前Servlet实例的webApplicationContext属性已经设置,则使用它
		// 对于完全使用缺省配置的Springboot Web应用,当前Servlet实例是DispatcherServlet,
		// 因为他实现了接口ApplicationContextAware,所以在它初始化时其属性
		// webApplicationContext已经被设置,这里将使用该属性
		// 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);
			}
		}
	}
	// 对于完全使用缺省配置的Springboot Web应用,从上面的分析可以得出结论,
	// Web app 的WebApplicationContext rootContext,和当前Servlet,也就是DispatcherServlet servlet
	// 的WebApplicationContext wac,最终指向了同一个对象
	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();
	}
	if (wac == null) {
		// No context instance is defined for this servlet -> create a local one
		wac = createWebApplicationContext(rootContext);
	}

	// 对于完全使用缺省配置的Springboot Web应用,这个标志初始化为false,并且没有被修改为true,
	// 所以对于DispatcherServlet,下面if与句中的逻辑会被执行
	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.
		// onRefresh是FrameworkServlet的空方法,供子类提供实现,而DispatcherServlet
		// 提供了相应的实现
		onRefresh(wac);
	}

	if (this.publishContext) {
		// Publish the context as a servlet context attribute.
		// 为当前Servlet构造属性名称,类似于 : 
		// org.springframework.web.servlet.FrameworkServlet.CONTEXT.dispatcherServlet
		String attrName = getServletContextAttributeName();
		// 将当前 Servlet 的 WebApplicationContext wac登记为servletContext的属性
		getServletContext().setAttribute(attrName, wac);
		// DispatcherServlet的初始化执行到这里,所属servletContext的属性中有两个属性:
		// 1.org.springframework.web.context.WebApplicationContext.ROOT
		// 2.org.springframework.web.servlet.FrameworkServlet.CONTEXT.dispatcherServlet
		// 并且它们指向了同一个WebApplicationContext,也就是 SpringApplication.run()方法中
		// 调用createApplicationContext()创建的那个
		// AnnotationConfigEmbeddedWebApplicationContext实例
		if (this.logger.isDebugEnabled()) {
			this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
					"' as ServletContext attribute with name [" + attrName + "]");
		}
	}

	return wac;
}

从上面的代码分析看出,对于一个完全采用缺省配置的Springboot web应用,里面只有唯一的一个DispatcherServlet实例,在这种情况下,该DispatcherServlet实例的Web ApplicationContext和整个应用的Root ApplicationContext是同一个。这是Spring MVC官方文档中介绍的一种用法,如下图所示:
Figure 22.3. Single root context in Spring Web MVC

以上是Spring MVC官方文档配图"Figure 22.3. Single root context in Spring Web MVC"。

DispatcherServletonRefresh方法

	// 类 org.springframework.web.servlet.DispatcherServlet 的方法
	protected void onRefresh(ApplicationContext context) {
		initStrategies(context);
	}
	
	/**
	 * Initialize the strategy objects that this servlet uses.
	 * 初始化当前Servlet使用的各种策略对象
	 * May be overridden in subclasses in order to initialize further strategy objects.
	 * 子类可以覆盖该方法用于初始化更多的策略对象
	 * 
	 */
	protected void initStrategies(ApplicationContext context) {
		// MultipartResolver 文件上传相关
		//   从bean容器中获取名字为MULTIPART_RESOLVER_BEAN_NAME(multipartResolver)的bean
		//   记录到属性multipartResolver,没有相应bean的话设置为NULL
		initMultipartResolver(context);
		// LocaleResolver, 本地化语言相关
		//   从bean容器中获取名字为LOCALE_RESOLVER_BEAN_NAME(localeResolver)的bean
		//   记录到属性localeResolver,没有相应bean的话使用缺省策略
		initLocaleResolver(context);
		// ThemeResolver 相关
		//   从bean容器中获取名字为THEME_RESOLVER_BEAN_NAME(themeResolver)的bean
		//   记录到属性themeResolver,没有相应bean的话使用缺省策略
		initThemeResolver(context);
		// HandlerMapping (list) 相关
		//   从bean容器中获取名字为HANDLER_MAPPING_BEAN_NAME(handlerMapping)的bean,
		//   或者获取所有类型是HandlerMapping的bean(DispatcherServlet的缺省做法),
		//   记录到属性handlerMappings,没有相应bean的话使用缺省策略
		initHandlerMappings(context);
		// HandlerAdapter (list) 相关
		//   从bean容器中获取名字为HANDLER_ADAPTER_BEAN_NAME(handlerAdapter)的bean,
		//   或者获取所有类型是HandlerAdapter的bean(DispatcherServlet的缺省做法),
		//   记录到属性handlerAdapters,没有相应bean的话使用缺省策略		
		initHandlerAdapters(context);
		// HandlerExceptionResolver (list) 异常处理Handler相关
		//   从bean容器中获取名字为HANDLER_EXCEPTION_RESOLVER_BEAN_NAME(handlerExceptionResolver)的bean,
		//   或者获取所有类型是HandlerExceptionResolver的bean(DispatcherServlet的缺省做法),
		//   记录到属性handlerExceptionResolvers,没有相应bean的话使用缺省策略			
		initHandlerExceptionResolvers(context);
		// RequestToViewNameTranslator 相关
		//   从bean容器中获取名字为REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME(viewNameTranslator)的bean
		//   记录到属性viewNameTranslator,没有相应bean的话使用缺省策略		
		initRequestToViewNameTranslator(context);
		// ViewResolver (list) 相关
		//   从bean容器中获取名字为VIEW_RESOLVER_BEAN_NAME(viewResolver)的bean,
		//   或者获取所有类型是ViewResolver的bean(DispatcherServlet的缺省做法),
		//   记录到属性viewResolvers,没有相应bean的话使用缺省策略			
		initViewResolvers(context);
		// FlashMapManager 相关
		//   从bean容器中获取名字为FLASH_MAP_MANAGER_BEAN_NAME(flashMapManager)的bean
		//   记录到属性flashMapManager,没有相应bean的话使用缺省策略		
		initFlashMapManager(context);
	}	

上面初始化过程中提到的9大策略组件,如果从bean容器中找不到相应的bean,会使用缺省值,而这些缺省值,定义在spring-webmvc jar包的文件org.springframework.web.servlet.DispatcherServlet.properties中,如下所示 :

# 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.annotation.DefaultAnnotationHandlerMapping

org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
	org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
	org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter

org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\
	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

关于上面9大组件的初始化过程,可以参考Spring MVC DispatcherServlet 策略初始化有关文章合集

#参考资料
Spring MVC是如何工作的?
缺省配置Springboot Web应用启动中准备 DispatcherServlet
缺省配置Springboot Web应用启动过程中Bean定义的登记
Spring MVC DispatcherServlet 策略初始化有关文章合集

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值