java dispatcher详解_【Spring MVC】DispatcherServlet详解(容器初始化超详细过程源码分析)...

本文详细介绍了SpringMVC的初始化过程,包括ContextLoaderListener监听器的启动、WebApplicationContext的创建和初始化,以及DispatcherServlet的初始化流程,特别是DispatcherServlet中九大核心组件的初始化。

DispatcherServlet类相关的结构图

0d9d60d9f210d9ca3f3916c0bd08d3bb.png

DispatcherServlet的初始化程序

DispatcherServlet初始化了什么,可以在其initStrategies()方法中知晓,这个方法如下:

protected void initStrategies(ApplicationContext context) {

initMultipartResolver(context);

initLocaleResolver(context);

initThemeResolver(context);

initHandlerMappings(context);

initHandlerAdapters(context);

initHandlerExceptionResolvers(context);

initRequestToViewNameTranslator(context);

initViewResolvers(context);

initFlashMapManager(context);

}

需要做的八件事情如下所述:

initMultipartResolver:初始化MultipartResolver,用于处理文件上传服务,如果有文件上传,那么就会将当前的HttpServletRequest包装成DefaultMultipartHttpServletRequest,并且将每个上传的内容封装成CommonsMultipartFile对象。需要在dispatcherServlet-servlet.xml中配置文件上传解析器。

initLocaleResolver:用于处理应用的国际化问题,本地化解析策略。

initThemeResolver:用于定义一个主题。

initHandlerMapping:用于定义请求映射关系。

initHandlerAdapters:用于根据Handler的类型定义不同的处理规则。

initHandlerExceptionResolvers:当Handler处理出错后,会通过此将错误日志记录在log文件中,默认实现类是SimpleMappingExceptionResolver。

initRequestToViewNameTranslators:将指定的ViewName按照定义的RequestToViewNameTranslators替换成想要的格式。

initViewResolvers:用于将View解析成页面。

initFlashMapManager:用于生成FlashMap管理器。

Spring MVC容器的初始化过程

首先,我们从web.xml中开始,在web.xml中我们首先配置的是contextLoaderListener,它的作用就是启动web容器时,自动装配ApplicationContext的配置信息。因为它实现了ServletContextListener这个接口,在web.xml配置这个监听器,启动容器时,就会自动执行它实现的contextInitialized()方法。这样就能够在客户端请求之前向ServletContext中添加任意的对象。

在ServletContextListener中的核心逻辑便是初始化WebApplicationContext实例并存放至ServletContext中。

public void contextInitialized(ServletContextEvent event) {

this.contextLoader = createContextLoader();

if (this.contextLoader == null) {

this.contextLoader = this;

}

this.contextLoader.initWebApplicationContext(event.getServletContext());

}

这是ContextLoaderListener中的contextInitialized()方法,这里主要是用initWebApplicationContext()方法来初始化WebApplicationContext。这里涉及到一个常用类WebApplicationContext:它继承自ApplicationContext,在ApplicationContext的基础上又追加了一些特定于Web的操作及属性。

这边的操作就非常类似通过编程的方式使用Spring时使用ClassPathXmlApplicationContext类提供的功能。

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {

if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {

throw new IllegalStateException(

"Cannot initialize context because there is already a root application context present - " +

"check whether you have multiple ContextLoader* definitions in your web.xml!");

}

Log logger = LogFactory.getLog(ContextLoader.class);

servletContext.log("Initializing Spring root WebApplicationContext");

if (logger.isInfoEnabled()) {

logger.info("Root WebApplicationContext: initialization started");

}

long startTime = System.currentTimeMillis();

try {

// Store context in local instance variable, to guarantee that

// it is available on ServletContext shutdown.

if (this.context == null) {

this.context = createWebApplicationContext(servletContext);

}

if (this.context instanceof ConfigurableWebApplicationContext) {

configureAndRefreshWebApplicationContext((ConfigurableWebApplicationContext)this.context, servletContext);

}

servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

ClassLoader ccl = Thread.currentThread().getContextClassLoader();

if (ccl == ContextLoader.class.getClassLoader()) {

currentContext = this.context;

}

else if (ccl != null) {

currentContextPerThread.put(ccl, this.context);

}

if (logger.isDebugEnabled()) {

logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +

WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");

}

if (logger.isInfoEnabled()) {

long elapsedTime = System.currentTimeMillis() - startTime;

logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");

}

return this.context;

}

catch (RuntimeException ex) {

logger.error("Context initialization failed", ex);

servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);

throw ex;

}

catch (Error err) {

logger.error("Context initialization failed", err);

servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);

throw err;

}

}

在initWebApplicationContext()方法中主要体现了WebApplicationContext实例的创建过程。首先,验证WebApplicationContext的存在性,通过查看ServletContext实例中是否有对应key的属性验证WebApplicationContext是否已经创建过实例。如果没有通过createWebApplicationContext()方法来创建实例,并存放至ServletContext中。

protected WebApplicationContext createWebApplicationContext(ServletContext sc) {

Class> contextClass = determineContextClass(sc);

if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {

throw new ApplicationContextException("Custom context class [" + contextClass.getName() +

"] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");

}

ConfigurableWebApplicationContext wac =

(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

return wac;

}

在createWebApplicationContext()方法中,通过BeanUtils.instanceClass()方法创建实例,而WebApplicationContext的实现类名称则通过determineContextClass()方法获得。

protected Class> determineContextClass(ServletContext servletContext) {

String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM);

if (contextClassName != null) {

try {

return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader());

}

catch (ClassNotFoundException ex) {

throw new ApplicationContextException(

"Failed to load custom context class [" + contextClassName + "]", ex);

}

}

else {

contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName());

try {

return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader());

}

catch (ClassNotFoundException ex) {

throw new ApplicationContextException(

"Failed to load default context class [" + contextClassName + "]", ex);

}

}

}

determineContextClass()方法,通过defaultStrategies.getProperty()方法获得实现类的名称,而defaultStrategies是在ContextLoader类的静态代码块中赋值的。具体的途径,则是读取ContextLoader类的同目录下的ContextLoader.properties属性文件来确定的。

也就是说,在初始化的过程中,程序会首先读取ContextLoader类的同目录下的属性文件ContextLoader.properties,并根据其中的配置提取将要实现WebApplicationContext接口的实现类,并根据这个类通过反射进行实例的创建。

综合以上的代码,ContextLoaderListener监听器的作用就是启动Web容器时,自动装配ApplicationContext的配置信息。因为它实现了ServletContextListener这个接口,在web.xml配置了这个监听器,启动容器时,就会默认执行它实现的contextInitialized()方法初始化WebApplicationContext实例,并放入到ServletContext中。由于在ContextLoaderListener中关联了ContextLoader这个类,所以整个加载配置过程由ContextLoader来完成。

diapatcherServlet的初始化过程

DispatcherServlet实现了Servlet接口的实现类。Servlet的生命周期分为3个阶段:初始化、运行和销毁。而其初始化阶段可分为:

Servlet容器加载Servlet类,把类的.class文件中的数据读到内存中;

Servlet容器中创建一个ServletConfig对象。该对象中包含了Servlet的初始化配置信息;

Servlet容器创建一个Servlet对象;

Servlet容器调用Servlet对象的init()方法进行初始化。

Servlet的初始化阶段会调用它的init()方法,DispatcherServlet也不例外,在它的父类HttpServletBean中找到了该方法。

public final void init() throws ServletException {

if (logger.isDebugEnabled()) {

logger.debug("Initializing servlet '" + getServletName() + "'");

}

// Set bean properties from init parameters.

try {

PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);

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) {

logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);

throw ex;

}

// Let subclasses do whatever initialization they like.

initServletBean();

if (logger.isDebugEnabled()) {

logger.debug("Servlet '" + getServletName() + "' configured successfully");

}

}

init()方法中先通过ServletConfigPropertiesValues()方法对Servlet初始化参数进行封装,然后再将这个Servlet转换成一个BeanWrapper对象,从而能够以spring的方式来对初始化参数的值进行注入。这些属性如contextConfigLocation、namespace等等。同时注册一个属性编辑器,一旦在属性注入的时候遇到Resource类型的属性就会使用ResourceEditor去解析。再留一个initBeanWrapper(bw)方法给子类覆盖,让子类处真正执行BeanWrapper的属性注入工作。但是HttpServletBean的子类FrameworkServlet和DispatcherServlet都没有覆盖其initBeanWrapper(bw)方法,所以创建的BeanWrapper对象没有任何作用。

程序接着往下走,运行到了initServletBean()方法。在之前,ContextLoaderListener加载的时候已经创建了WebApplicationContext实例,而在这里是对这个实例的进一步补充初始化。这个方法在HttpServletBean的子类FrameworkServlet中得到了重写。

注:ContextLoaderListener载入的是除DispatcherServlet之外的其他的上下文配置文件,而Spring MVC的配置文件是在DispatcherServlet中设置的。

protected final void initServletBean() throws ServletException {

getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");

if (this.logger.isInfoEnabled()) {

this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");

}

long startTime = System.currentTimeMillis();

try {

this.webApplicationContext = initWebApplicationContext();

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");

}

}

在initServletBean()方法中,设计了一个计时器来统计初始化的执行时间,还提供了一个initFrameworkServlet()用于子类的覆盖操作,而作为关键的初始化逻辑给了initWebApplicationContext()方法。

protected WebApplicationContext initWebApplicationContext() {

WebApplicationContext rootContext =

WebApplicationContextUtils.getWebApplicationContext(getServletContext());

WebApplicationContext wac = null;

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);

}

}

}

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);

}

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(wac);

}

if (this.publishContext) {

// Publish the context as a servlet context attribute.

String attrName = getServletContextAttributeName();

getServletContext().setAttribute(attrName, wac);

if (this.logger.isDebugEnabled()) {

this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +

"' as ServletContext attribute with name [" + attrName + "]");

}

}

return wac;

}

initWebApplicationContext()方法主要用于创建或刷新WebApplicationContext实例,并对Servlet功能所使用的变量进行初始化。它获得ContextLoaderListener中初始化的rootContext。再通过构造函数和Servlet的contextAttribute属性查找ServletContext来进行webApplicationContext实例的初始化,如果都不行,只能重新创建一个新的实例。最终都要执行configureAndRefreshWebApplicationContext()方法中的refresh()方法完成servlet中配置文件的加载和与rootContext的整合。

再下来,重点来了:

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(wac);

}

onRefresh(wac)方法是FrameworkServlet提供的模板方法,在其子类DispatcherServlet的onRefresh()方法中进行了重写。而在onRefresh()方法中调用了initStrategies()方法来完成初始化工作,初始化Spring MVC的9个组件。

@Override

protected void onRefresh(ApplicationContext context) {

initStrategies(context);

}

具体的时序图

9fcb41e4dd66541e485b12867b24915c.png

来源:[]()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值