深入Spring源碼系列----如何取消web.xml和Spring.xml這兩個繁重的配置文件

我們知道,SpringMVC結合Spring最重要的兩個文件是web.xml和Spring.xml,但是我們通過了註解的方式取消了這兩個文件,這個通過什麼方式解決這些繁瑣的配置文件的呢?首先我們來看web.xml這個文件是如何替換的呢,這個是由於Tomcat啓動應用的時候,首先回去找META-INF/services目錄下的javax.servletContainerInitializer文件,這個文件中存放着實現了ServletContainerInitializer接口的類,然後調用裏面的onStartup方法

public void onStartup(Set<Class<?>> set, ServletContext servletContext)

這個方法有兩個入參,其中這個set集合中的class類型是通過這個類上面的註解
@HandlesTypes(WebApplicationInitializer.class)
中的類型。當Tomcat啓動的時候就會去調用這個類中onStartup()方法了,所以我們可以想象到在這個onStartup()方法中就會完成原web.xml配置文件所實現的功能。而web.xml中有兩個非常重要的元素:一個是Listener元素

<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

另一個是DispatcheServlet元素

<servlet>
  <servlet-name>spring-dispatcher</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <init-param>
    <!--springmvc的配置文件-->
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:spring-dispatcher.xml</param-value>
  </init-param>
  <load-on-startup>0</load-on-startup>
</servlet>
<servlet-mapping>
  <servlet-name>spring-dispatcher</servlet-name>
  <url-pattern>/</url-pattern>
</servlet-mapping>

現在我們的關注點就是如何將這兩個元素的實例化。

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
   @Override
   //此處的webAppInitializerClasses這個參數爲項目中實現了WebApplicationInitializer接口的類
   public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
         throws ServletException {

      List<WebApplicationInitializer> initializers = new LinkedList<>();

      if (webAppInitializerClasses != null) {
         for (Class<?> waiClass : webAppInitializerClasses) {
            // Be defensive: Some servlet containers provide us with invalid classes,
            // no matter what @HandlesTypes says...
            if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
                  WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
               try {
                  //通過反射構造對象並且加入到集合中
                  initializers.add((WebApplicationInitializer)
                        ReflectionUtils.accessibleConstructor(waiClass).newInstance());
               }
               catch (Throwable ex) {
                  throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
               }
            }
         }
      }

      if (initializers.isEmpty()) {
         servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
         return;
      }

      servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
      //將集合中的對象排序
      AnnotationAwareOrderComparator.sort(initializers);
      for (WebApplicationInitializer initializer : initializers) {
         //循環調用WebApplicationInitializer類型對象中的onStartup方法(鉤子方法)
         //AbstractDispatcherServletInitializer這個中的onStartup方法完成Listener和Dispatcher兩種對象的註冊
         initializer.onStartup(servletContext);
      }
   }

}

主要是通過,獲取到項目中實現了WebApplicationInitializer接口的方法,然後通過循環調用這個接口中的onStartup將Listener和Dispatcher這兩個對象進行註冊。AbstraceDispatcherServletInitializer這個中的onStartup方法完成的。這個抽象類中的onStartup方法先是註冊Listener對象,即調用registerContextLoaderListener方法,這個方法首先創建Spring的上下文對象。這個上下文對象具體是如何創建的呢?它會調用到createRootApplicationContext方法,這個是個抽象方法,是個鉤子方法,它會調用到子類的方法(AbstractAnnotationConfigDispatcherServletInitializer這個子類,它也是個抽象類)。

protected WebApplicationContext createRootApplicationContext() {
   //鉤子方法,調用自己實現的AbstractAnnotationConfigDispatcherServletInitializer中的方法
   //主要就是將有這個@ComponentScan註解類型的類加載進來
   Class<?>[] configClasses = getRootConfigClasses();
   if (!ObjectUtils.isEmpty(configClasses)) {
      //創建註解上下文環境
      AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
      context.register(configClasses);
      return context;
   }
   else {
      return null;
   }
}
主要是通過鉤子方法將創建Spring註解的上下文環境,然後將包含了@ComponentScan註解的類注測到Spring中。接着我們就獲取到了Spring的上下文環境,接着我們就是創建監聽器(Listener)了,至此我們就完成了一個重要對象Listener的創建了。下一個就是Dispatcher對象的創建了。回到我們的AbstractDispatcherServletInitializer對象中的onStartup方法中來,通過registerDispatcherServlet方法我們要完成Dispatcher對象的註冊。
protected void registerDispatcherServlet(ServletContext servletContext) {
   String servletName = getServletName();
   Assert.hasLength(servletName, "getServletName() must not return null or empty");

   //創建springmvc的上下文,註冊了MvcContainer類,主要是將包含了@ComponentScan註解的類註冊到上下文中
   WebApplicationContext servletAppContext = createServletApplicationContext();
   Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");

   //創建DispatcherServlet
   FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
   Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");
   dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());
   
   ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
   if (registration == null) {
      throw new IllegalStateException("Failed to register servlet with name '" + servletName + "'. " +
            "Check if there is another servlet registered under the same name.");
   }

   /*
   * 如果該元素的值爲負數或者沒有設置,則容器會當Servlet被請求時再加載。
      如果值爲正整數或者0時,表示容器在應用啓動時就加載並初始化這個servlet,
      值越小,servlet的優先級越高,就越先被加載
   * */
   registration.setLoadOnStartup(1);
   registration.addMapping(getServletMappings());
   registration.setAsyncSupported(isAsyncSupported());
   //鉤子方法,調用到自己實現的方法
   Filter[] filters = getServletFilters();
   if (!ObjectUtils.isEmpty(filters)) {
      for (Filter filter : filters) {
         //註冊過濾器到servlet上下文環境中來
         registerServletFilter(servletContext, filter);
      }
   }

   customizeRegistration(registration);
}

至此兩個對象就註冊好了,同時對應的上下文環境也初始化完成了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章