詳解springMVC容器載入原始碼分析

NO IMAGE

springmvc是一個基於servlet容器的輕量靈活的mvc框架,在它整個請求過程中,為了能夠靈活定製各種需求,所以提供了一系列的元件完成整個請求的對映,響應等等處理。這裡我們來分析下springMVC的原始碼。

首先,spring提供了一個處理所有請求的servlet,這個servlet實現了servlet的介面,就是DispatcherServlet。把它配置在web.xml中,並且處理我們在整個mvc中需要處理的請求,一般如下配置:


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

  <servlet-mapping>
    <servlet-name>spring-servlet</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>

DispatcherServlet也繼承了FrameworkServlet抽象類,這個抽象類為整個servlet的處理提供了spring容器的支援,讓原本servlet容器的DispatcherServlet擁有能夠利用spring容器元件的能力。上面servlet的初始化引數contextConfigLocation就是DispatcherServlet獲取spring容器的配置環境。FrameworkServlet繼承自org.springframework.web.servlet.HttpServletBean。HttpServletBean實現了servlet容器初始化會呼叫的init函式。這個init函式會呼叫FrameworkServlet的初始化載入spring容器方法。方法原始碼:


@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 {
     // 初始化spring-servlet容器
      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");
    }
  }

可以看到這裡就觸發了spring的對web支援的容器初始化,這裡使用的容器為WebApplicationContext.接下來我們就來分析一下整個容器的初始化過程:


protected WebApplicationContext initWebApplicationContext() {
// 檢視是否在servlet上下文有所有容器需要繼承的根Web容器
    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) {
      // 建立容器
      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;
  }

如果已經有容器被建立那就初始化。否則建立容器,建立邏輯:


protected WebApplicationContext createWebApplicationContext(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);

    wac.setEnvironment(getEnvironment());
    wac.setParent(parent);
    wac.setConfigLocation(getContextConfigLocation());
    // 這裡是獲取配置檔案和完成初始化web容器的入口
    configureAndRefreshWebApplicationContext(wac);

    return wac;
  }

這裡完成了 web容器的相關準備工作,開始正式讀取配置檔案載入和初始化容器。


protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
// 給容器設定一個id
    if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
      // The application context id is still set to its original default value
      // -> assign a more useful id based on available information
      if (this.contextId != null) {
        wac.setId(this.contextId);
      }
      else {
        // Generate default id...
        wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX  
            ObjectUtils.getDisplayString(getServletContext().getContextPath())   '/'   getServletName());
      }
    }

    wac.setServletContext(getServletContext());
    wac.setServletConfig(getServletConfig());
    wac.setNamespace(getNamespace());
    wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

    // The wac environment's #initPropertySources will be called in any case when the context
    // is refreshed; do it eagerly here to ensure servlet property sources are in place for
    // use in any post-processing or initialization that occurs below prior to #refresh
    ConfigurableEnvironment env = wac.getEnvironment();
    if (env instanceof ConfigurableWebEnvironment) {
      ((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
    }
    /* 這裡在容器被啟用後,
    並且容器還沒完成初始化之前可以對容器的相關配置做一些修改,
    預設給了空實現,
    這裡子類可以去重寫,能夠獲得在容器初始化之前做  一些處理*/
    postProcessWebApplicationContext(wac);
    // 這裡講容器的初始化資訊放到一個列表
    applyInitializers(wac);
    // 這裡就開始web容器的初始化
    wac.refresh();
  }

容器的初始化在AbstractApplicationContext,無論是其他的容器,最終都會呼叫到refresh()函式,這個函式基本定義了整個容器初始化的整個脈絡,這裡不展開講,本部落格之後應該會詳細分析這塊的邏輯,這裡大概的註釋一下每一個函式完成的操作:


public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
      // 這裡主要載入了容器當中一些從其他配置檔案讀取的變數
      prepareRefresh();

      // 獲取容器本身
      ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

      // 這裡完成一些基礎元件的依賴
      prepareBeanFactory(beanFactory);

      try {
        // 新增 容器初始化之前的前置處理
        postProcessBeanFactory(beanFactory);

        // 呼叫 前置處理器,其中包含invokeBeanDefinitionRegistryPostProcessors與invokeBeanFactoryPostProcessors兩類前置處理器的呼叫
        invokeBeanFactoryPostProcessors(beanFactory);

        // 註冊bean被建立之前的前置處理器
        registerBeanPostProcessors(beanFactory);

        // 初始化容器的編碼源        
        initMessageSource();

        // 初始化一些事件監聽器
        initApplicationEventMulticaster();

        // Initialize other special beans in specific context subclasses.
        onRefresh();

        // 註冊容器監聽器
        registerListeners();

        // 初始化所有非懶載入的beans
        finishBeanFactoryInitialization(beanFactory);

        // Last step: 事件通知關心容器載入的相關元件
        finishRefresh();
      }

      // 部分程式碼省略
      }
    }
  }

自此載入完畢核心容器,然後回到FramewordServlet的initWebApplicationContext函式,在呼叫createWebApplicationContext完成一系列上面的操作之後,需要mvc servlet元件,入口就是onRefresh(ApplocationContext context)方法。它會呼叫另一個方法initStrategies(ApplicationContext context)。該方法如下:


protected void initStrategies(ApplicationContext context) {
    initMultipartResolver(context);
    initLocaleResolver(context);
    initThemeResolver(context);
    // 獲取所有的RequestMappings
    initHandlerMappings(context);
    // 不同handler的介面卡
    initHandlerAdapters(context);
    // 異常解析器
    initHandlerExceptionResolvers(context);
    initRequestToViewNameTranslator(context);
    initViewResolvers(context);
    initFlashMapManager(context);
  }

這裡我們重點講解initHandlerMappings與initHandlerAdapters函式,因為這兩個是處理servlet請求的入口。

在spring mvc中任何類都可以處理request請求,因為DispacherServlet也是實現了HttpServlet的介面,所以處理請求也是doService裡。doService會將請求交給doDispatcher函式處理。然後doDispacher的原始碼:

但是我們發現獲取到的handler並不是Object而是一個HandlerExecutionChain,這個類可以進去檢視發現是一堆攔截器和一個handler,主要就是給每一個請求一個前置處理的機會,這裡值得一提的是一般來說攔截器和過濾器的區別就是攔截器可以終止後續執行流程,而過濾器一般不終止。過濾器一般是容器級別的,這個handler前置攔截器可以做到更細級別的控制,例如過濾器只定義了init和doFilter,但是這個handler攔截器定義了preHandle和postHandle還有afterCompletion函式,不難理解分別對應不同請求階段的攔截粒度,更加靈活。

獲取處理handler的getHandler程式碼:

HandlerAdapter處理後返回統一被封裝成ModelAndView物件,這個就是包含試圖和資料的物件,在經過適當的處理:

將頁面與返回的資料返回給瀏覽器完成整個請求過程。以上就是springMVC大概的啟動過程和請求的處理過程,之後本部落格還會陸續分析核心的spring原始碼,博主一直認為精讀著名框架原始碼是在短時間提升程式碼能力的捷徑,因為這些輪子包含太多設計思想和細小的程式碼規範,這些讀多了都會潛移默化的形成一種本能的東西。設計模式也不斷貫穿其中。