DispatcherServlet的启动和初始化

本节一起学习DispatcherServlet的启动和初始化

前面已经分析了Spring MVC的工作,下面就DispatcherServlet的启动和初始化进行详细的分析

作为Servlet,DispatcherServlet的启动和Servlet的启动过程是相关联的,在Servlet的初始化过程中,Servlet的init会被调用,进行初始化。下面看一下DispatcherServlet的基类HttpServletBean中的初始化过程。

	@Override
	public final void init() throws ServletException {
		if (logger.isDebugEnabled()) {
			logger.debug("Initializing servlet '" + getServletName() + "'");
		}

		// 获取Servlet的初始化参数,对Bean属性进行配置
		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;
			}
		}

		// 调用子类的initServletBean进行具体的初始化
		initServletBean();

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

在初始化开始时,要读取配置在ServletContext中的Bean属性参数,这些属性参数设置在web.xml的Web容器初始化参数中。使用编程式的方式设置这些Bean属性。接着会执行DispatcherServlet持有的IOC容器的初始化过程,在这个初始化过程中,一个新的上下文被建立起来,这个DispatcherServlet持有的上下文被设置为根上下文的子上下文。根上下文和Web应用相对应的一个上下文,而DispatcherServlet持有的上下文时和Servlet相对性的一个上下文。DispatcherServlet持有的上下文被建立起来后,也需要和其他IOC容器一样完成初始化,这个初始化也是通过refresh方法完成的。最后,DispatcherServlet给自己持有上下文命名,并把它设置到Web容器的上下文中,这个名词和web.xml设置的DispatcherServlet的Servlet的名称有关,从而保证这个上下文在web环境上下文体系中的唯一性。

	@Override
	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 | 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");
		}
	}
	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) {
			// 把当前建立的上下文存到ServletContext中
			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;
	}

在这里MVC的上下文建立起来了,具体获取根上下文的过程在WebApplicationContextUtils中实现。

	@Nullable
	public static WebApplicationContext getWebApplicationContext(ServletContext sc) {
		return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
	}
	@Nullable
	public static WebApplicationContext getWebApplicationContext(ServletContext sc, String attrName) {
		Assert.notNull(sc, "ServletContext must not be null");
		Object attr = sc.getAttribute(attrName);
		if (attr == null) {
			return null;
		}
		if (attr instanceof RuntimeException) {
			throw (RuntimeException) attr;
		}
		if (attr instanceof Error) {
			throw (Error) attr;
		}
		if (attr instanceof Exception) {
			throw new IllegalStateException((Exception) attr);
		}
		if (!(attr instanceof WebApplicationContext)) {
			throw new IllegalStateException("Context attribute is not of type WebApplicationContext: " + attr);
		}
		return (WebApplicationContext) attr;
	}

这个根上下文是ContextLoader设置到ServletContext中的,使用的属性是ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,同时对这个IOC容器的Bean配置文件,ContextLoader也进行了设置,默认的位置在/WEB-INF/applicationContext.xml中。由于这个根上下文是DispatcherServlet建立的上下文的双亲上下文,所以跟上下文中管理的Bean也是可以被DispatcherServlet的上下文使用的。通过getBean向IOC容器获取Bean时,容器会先到它的双亲Ioc容器中获得getBean。

	protected WebApplicationContext createWebApplicationContext(@Nullable WebApplicationContext parent) {
		return createWebApplicationContext((ApplicationContext) parent);
	}
	protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
		Class<?> contextClass = getContextClass();
		if (this.logger.isDebugEnabled()) {
			this.logger.debug("Servlet with name '" + getServletName() +
					"' will try to create custom WebApplicationContext context of class '" +
					contextClass.getName() + "'" + ", using parent context [" + parent + "]");
		}
		if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
			throw new ApplicationContextException(
					"Fatal initialization error in servlet with name '" + getServletName() +
					"': custom WebApplicationContext class [" + contextClass.getName() +
					"] is not of type ConfigurableWebApplicationContext");
		}
		ConfigurableWebApplicationContext wac =
				(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
                //使用反射实例化上下文,默认的XmlWebApplicationContext
		wac.setEnvironment(getEnvironment());
		wac.setParent(parent);//设置双亲IOC
		String configLocation = getContextConfigLocation();
		if (configLocation != null) {
			wac.setConfigLocation(configLocation);
		}//初始化
		configureAndRefreshWebApplicationContext(wac);

		return wac;
	}
这里创建上下文的过程与建立根上下文的过程很类似,建立DispatcherServlet的上下文需要把根上下文参数传递给它。然后使用反射技术实例化上下文对象,并为它设置参数。最后通过refresh完成IOC容器的最终初始化。

在Spring MVC DispatcherServlet的初始化过程中,以对HandlerMapping的初始化调用作为触发点,了解Spring MVC模块初始化的方法调用关系。这个调用关系最初是由HttpServletBean的init方法触发的,这个HttpServletBean是HttpServlet的子类,接着会在HttpServletBean的子类FrameworkServlet对IOC容器完成初始化,在初始化中,会调用DispatcherServlet的initStrategies方法,这个initStrategies方法,启动整个Spring MVC框架的初始化。

	protected void initStrategies(ApplicationContext context) {
		initMultipartResolver(context);
		initLocaleResolver(context);
		initThemeResolver(context);
		initHandlerMappings(context);
		initHandlerAdapters(context);
		initHandlerExceptionResolvers(context);
		initRequestToViewNameTranslator(context);
		initViewResolvers(context);
		initFlashMapManager(context);
	}

initStrategies完成了对各种MVC框架的实现元素,比如支持国际化的LocaleResolver、支持request映射的HandlerMappings以及视图生成的ViewResolvers等的初始化。

以HandlerMappings为例子,说明这个initHandlerMappings的过程,这里的Mapping关系的作用是,为Http请求找到相应的Controller控制器,从而利用这些控制器Controller完成设计好的数据处理工作。HandlerMappings完成对MVC中Controller的定义和配置,只不过在Web特定的应用环境中,这些控制器是与具体的HTTP请求相对应的。

	private void initHandlerMappings(ApplicationContext context) {
		this.handlerMappings = null;
                //detectAllHandlerMappings默认为true,这里导入所有的HandlerMappings Bean,这些Bean可以在当前的DispatcherServlet的IOC容器中,也可能在双亲上下文中
		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 {//也可以根据名称从当前的IOC容器中通过getBean获得handlerMapping
			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.
			}
		}

		// 如果没有找到handlerMapping,那么需要Servlet设定默认的handlerMappings
		if (this.handlerMappings == null) {
			this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
			if (logger.isDebugEnabled()) {
				logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
			}
		}
	}

猜你喜欢

转载自blog.csdn.net/dxh0823/article/details/80717819