Spring MVC探秘之初始化

  这篇文章主要分析Spring MVC的初始化过程,力争把Spring MVC的初始化完整清晰的表达出来。

总体继承结构

  Spring MVC的初始化和处理过程主要涉及三个类,分别是HttpServletBean、FrameworkServlet、DispatcherServlet,其继承关系如下图所示:
在这里插入图片描述
  从图中可以看到,HttpServletBean不仅继承了HttpServlet类,还继承了EnvirenmentAware和EnviromentCapable接口,FrameworkServlet实现了ApplicationContextAware接口,那么这三个接口的作用是什么呢?

aware接口

  aware接口是一个具有标识作用的超级接口,实现该接口的bean是具有被spring 容器通知的能力的,xxxAware在Spring中表示对xxx可感知,spring可以通过接口中的setxxx方法将xxx属性送入实现了间接或直接实现了此接口的类中。例如HttpServlet实现了EnvironmentAware,那么Spring便能够在自动在初始化时通过setEnvironment方法将environment送入HttpServlet中,ApplicationContextAware接口同理。

capable接口

  capable接口同样也是一个表示接口,不过它和aware接口相反,它表示具有提供出某种属性的能力,如EnvironmentCapable接口表示具有向Spring提供Environment的能力,通过其唯一方法getEnvironment()。

environment

  Environment即环境,相当于是是我们的应用程序运行时的一个背景信息。在spring中,Environment这个概念是与spring的容器container集成在一起的一个抽象概念。它主要为我们的应用程序环境的两个方面的支持:profiles and properties。在spring库中,Environment也有其接口的代码定义,如下所示:

public interface Environment extends PropertyResolver {

	String[] getActiveProfiles();
	
	String[] getDefaultProfiles();

	boolean acceptsProfiles(String... profiles);
}
  • profile
    • 一个profile是一组Bean定义(Bean definition)的逻辑分组(logical group)。
    • 这个分组,也就是这个profile,被赋予一个命名,就是这个profile的名字。
    • 只有当一个profile处于active状态时,它对应的逻辑上组织在一起的这些Bean定义才会被注册到容器中。
    • Bean添加到profile可以通过XML定义方式或者annotation注解方式。
    • Environment对于profile所扮演的角色是用来指定哪些profile是当前活跃的或是缺省活跃的。
  • properties
    • 一个应用的属性有很多来源: 属性文件(properties files),JVM系统属性,系统环境变量,JNDI,servlet上下文参数,临时属性对象等。
    • Environment对于property所扮演的角色是提供给使用者一个方便的服务接口用于配置属性源和从属性源中获取属性

ApplicationContext大家都比较熟悉,这边就不详细介绍了。

源码剖析

HttpServletBean

在servlet创建时会直接调用init方法,HttpServletBean的init方法代码如下:

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

		// 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;
			}
		}

		// Let subclasses do whatever initialization they like.
		initServletBean();

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

FrameworkServlet

  FrameworkServlet中initServletBean方法代码如下:

@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 {
      //初始化webApplicationContext
      this.webApplicationContext = initWebApplicationContext();
      //交给子类实现,但是spring中并没有子类实现了这个方法,估计是当需要自定义一些逻辑时,由用户实现
      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中,最主要的方法便是initWebAplicationContext()和initFrameworkServlet(),initWebAplicationContext用来初始化tWebAplicationContext,initFrameworkServlet用来初始化FrameworkServlet,但是在Spring自身里面并没有子类实现了initFrameworkServlet方法,因此可知initServletBean主要工作便是初始化了WebAplicationContext。

protected WebApplicationContext initWebApplicationContext() {
   //获取根上下文,具体见下面解释,根上下文由contextLoaderListener创建,并保存在ServletContext中。
   WebApplicationContext rootContext =
         WebApplicationContextUtils.getWebApplicationContext(getServletContext());
   WebApplicationContext wac = null;
    //如果构造方法中已经传入webApplicationContext则使用
   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);
            }
            //配置并刷新webApplicationContext容器
            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
      //如果Servlet中直接配置了WebApplicationContext,则将其取出。
      wac = findWebApplicationContext();
   }
   if (wac == null) {
      // No context instance is defined for this servlet -> create a local one
      //如果WebApplicationContext尚未被创建,则在这里创建WebApplicationContext
      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里,首先进行的工作是获取根上下文,那么根上下文是什么时候创建好,又是以什么方式提供出来的呢。

根上下文rootContext

  根上下文是在web.xml里配置的contextLoaderListener创建的,在web容器启动时会触发容器初始化事件,contextLoaderListener监听到这个事件后,其ContextInitialized方法便会被调用,在这个方法中,spring会初始化一个启动上下文,这个上下文就是根上下文,也就是WebApplicationContext,实际实现类一般是XmlWebApplicationContext,创建完之后Spring会以WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE属性为Key,将根上下文存储到ServletContext中,因此调用根上下文时只需要调用ServletContext的getAttribute方法便可以了,在initWebApplicationContext中便是这么做的。

public static WebApplicationContext getWebApplicationContext(ServletContext sc) {
   return getWebApplicationContext(sc, WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
}

public static WebApplicationContext getWebApplicationContext(ServletContext sc, String attrName) {
   Assert.notNull(sc, "ServletContext must not be null");
   Object attr = sc.getAttribute(attrName);
   ······
   return (WebApplicationContext) attr;
}

  在获取到根上下文之后,便是创建属于DispatcherServlet自身的WebApplicationContext,一般类型也是XmlWebApplicationContext,但这两个WebApplicationContext的区别是什么呢?

  1. ContextLoaderListener中创建ApplicationContext主要用于整个Web应用程序需要共享的一些组件,比如DAO,数据库的ConnectionFactory等。而由DispatcherServlet创建的ApplicationContext主要用于和该Servlet相关的一些组件,比如Controller、ViewResovler等。
  2. 对于作用范围而言,在DispatcherServlet中可以引用由ContextLoaderListener所创建的ApplicationContext,而反过来不行。
WebApplicationContext

  下面具体介绍WebApplicationContext的初始化过程。设置WebApplicationContext一共有三个方法。

  1. WebApplicationContext已存在,即在构造方法中已经传入WebApplicationContext,此时直接使用便可。
  2. 第二种是WebApplicationContext已存在于ServletContext中,并且key为contextAttribute,例如可以在web.xml中init_param里设置WebApplicationContext。
  3. 如果前两种方法都不能获取到WebApplicationContext,那么只能自己创建一个WebApplicationContext,无特殊情况时便采用这种方式。创建过程在createWebApplicationContext方法中。
protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
  //获取需要创建的WebApplicationContext的具体类型
   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);
   wac.setEnvironment(getEnvironment());
   wac.setParent(parent);
   wac.setConfigLocation(getContextConfigLocation());
   configureAndRefreshWebApplicationContext(wac);
   return wac;
}

  在createWebApplicationContext方法中,首先获取需要创建的WebApplicationContext的具体类型,它可以通过contextClass属性设置在Servlet中,默认为 org.springframework.web.context.support.XmlWebApplicationContext,然后检查类型是否属于ConfigurableWebApplicationContext类型,为否则抛出异常。之后通过BeanUtils创建实例,创建完之后将environment,根上下文,contextConfigLocation传入wac。最后通过configureAndRefreshWebApplicationContext方法将contextId,ServletContext,ServletConfig,namespace传入wac,同时还为wac添加了监听器,监听器的类型为SourceFilteringListener,其实际监听的是传入的ContextRefreshListener所监听的事件,ContextRefreshListener是FrameworkServlet的内部类,监听ContextRefreshEvent事件,当接收到消息时调用FrameWorkServlet的onApplicationEvent方法,在其中会调用一次OnRefresh方法,并将refreshEventReceived标志置为true,表示已经refresh过。
  在完成wac的创建之后,再次回到initWebApplicationContext方法,此时会根据refreshEventReceived标志来判断是否需要调用OnRefresh方法,可知第三种方法创建的wac不需要在进行OnRefresh方法。不管怎样,Spring保证onRefresh方法会且只会被调用一次。这个方法由DispatcherServlet方法重写,也是DisPatcherServlet在整个初始化过程中唯一要做的工作。

DisPatcherServlet

@Override
protected void onRefresh(ApplicationContext context) {
   initStrategies(context);
}
/**
 * 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);
}

  在onRefresh方法中简单调用了initStrategies方法,在其中初始化了九个组件。为什么不在onRefresh防止中直接初始化而是要调用initStrategies来进行初始化呢,主要是为了逻辑上的分层,因为onRefresh只是用来刷新容器,而initStrategies是为了对组件进行初始话,如果在刷新中还想添加其他功能,逻辑就会变得有些胡乱。同时initStrategies也能被其他方法调用用来进行初始化,而如果没有initStrategies方法,则只能调用onRefresh方法,这就限制了onRefresh添加其他的方法,因此这么做不管是在可扩展性还是在语义上都会产生便利性。到这边为止Spring MVC的初始化便完成了,可以看出主要就是初始化了WebApplicationContext。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值