springMVC(11) springMVC初始化

前言

<servlet>
    <servlet-name>spring-mvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:config/spring-mvc.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>spring-mvc</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

根據(jù)web.xml的配置,我們可以知道springMVC的主體控制器部分就是DispatcherServlet,同時也不難看出DispatcherServlet的本質其實就是一個Servlet。那我們這次來剖析一下DispatcherServlet的初始化流程。

DispatcherServlet類圖

DispatcherServlet類圖

其中HttpServletBean、FrameworkServlet和DispatcherServlet都是springMVC框架中的東西。

HttpServletBean類的init方法

寫java web時,我們知道如果需要初始化,那我們就需要重載HttpServlet的init方法。那么我們首先來尋找init方法的位置,可以發(fā)現(xiàn)它在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 they like.
   initServletBean();

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

該方法主要做了如下兩件事:

  1. 將初始化參數(shù)設置為該servlet的屬性
  2. 調用子類的initServletBean方法

附:以我的理解來解釋一下初始化參數(shù)設置為屬性這一步驟,

<servlet>
    <servlet-name>spring-mvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:config/spring-mvc.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>spring-mvc</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

在配置文件中我們可以發(fā)現(xiàn)DispatcherServlet的初始化參數(shù)為contextConfigLocation=classpath:config/spring-mvc.xml,我們可以在FrameworkServlet的屬性列表中找到contextConfigLocation這個屬性。那么這塊地方做的工作就是將初始化參數(shù)中contextConfigLocation的值配置給DispatcherServlet的contextConfigLocation屬性。

FrameworkServlet類的initServletBean方法

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

該函數(shù)中主要調用了兩個方法:initWebApplicationContext()和initFrameworkServlet()。

initFrameworkServlet()是用來給子類做初始化擴展的,事實上FrameworkServlet中是一個空實現(xiàn),DispatchServlet也并沒有重載該方法。

FrameworkServlet類的initWebApplicationContext方法

初始化WebApplicationContext,并且將根WebApplicationContext設置為其父context。

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

DispatcherServlet類的onRefresh方法

我們可以發(fā)現(xiàn),無論怎樣,initWebApplicationContext方法最終都會調用到onRefresh方法。onRefresh在DispatcherServlet中被重載了,那我們直接看onRefresh部分的源碼。

protected void onRefresh(ApplicationContext context) {
   initStrategies(context);
}

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方法,而initStrategies則對MultipartResolver, LocaleResolver, ThemeResolve, HandlerMappings, HandlerAdapters, HandlerExceptionResolvers, RequestToViewNameTranslator, ViewResolvers和FlashMapManager做了初始化。

不難知道,我們之前閱讀的HandlerMapping, HandlerAdapter及ViewResolver等都是在這一步進行初始化的操作。

initHandlerMappings

我們取其中的HandlerMapping的初始化來繼續(xù)分析初始化流程,

private void initHandlerMappings(ApplicationContext context) {
   this.handlerMappings = null;

   // 如果detectAllHandlerMappings屬性設置為真,則從用戶配置文件中查找所有的HandlerMapping,并排序
   if (this.detectAllHandlerMappings) {
      Map<String, HandlerMapping> matchingBeans =
            BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
      if (!matchingBeans.isEmpty()) {
         this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
         OrderComparator.sort(this.handlerMappings);
      }
   }
   // 否則只使用名稱為"handlerMapping"的HandlerMapping
   else {
      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,則使用默認配置
   if (this.handlerMappings == null) {
      this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
      if (logger.isDebugEnabled()) {
         logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
      }
   }
}

從源碼中可以知道,HandlerMapping的初始化流程主要分為兩種,

  1. 使用用戶自己的配置
  2. 使用默認配置

對于自己的配置沒有什么可說的,那我們接下來來看默認配置,

protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
   String key = strategyInterface.getName();
   String value = defaultStrategies.getProperty(key);
   if (value != null) {
      String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
      List<T> strategies = new ArrayList<T>(classNames.length);
      for (String className : classNames) {
         try {
            Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
            Object strategy = createDefaultStrategy(context, clazz);
            strategies.add((T) strategy);
         }
         catch (ClassNotFoundException ex) {
            throw new BeanInitializationException(
                  "Could not find DispatcherServlet's default strategy class [" + className +
                        "] for interface [" + key + "]", ex);
         }
         catch (LinkageError err) {
            throw new BeanInitializationException(
                  "Error loading DispatcherServlet's default strategy class [" + className +
                        "] for interface [" + key + "]: problem with class file or dependent class", err);
         }
      }
      return strategies;
   }
   else {
      return new LinkedList<T>();
   }
}

我們可以看到里面有一個關鍵的屬性defaultStrategies。我們來看看這個defaultStrategies到底是怎么一回事,

private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";

private static final Properties defaultStrategies;

static {
   // Load default strategy implementations from properties file.
   // This is currently strictly internal and not meant to be customized
   // by application developers.
   try {
      ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
      defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
   }
   catch (IOException ex) {
      throw new IllegalStateException("Could not load 'DispatcherServlet.properties': " + ex.getMessage());
   }
}

可以發(fā)現(xiàn)defaultStrategies里的屬性來自于DispatcherServlet.properties這個文件,那么我們繼續(xù)找到該文件,

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

到這里我們的疑惑應該就已經全部解清了。也知道之前閱讀的HandlerMapping、HandlerAdater等默認值為什么是那些了。當然了,如果我們只需要其中的部分HandlerMapping等,也并不需要修改該文件,從getDefaultStrategies函數(shù)中我們可以知道,只需要我們在配置文件中有定義相應的屬性,那么就不會使用默認的。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容