深入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);
}

至此两个对象就注册好了,同时对应的上下文环境也初始化完成了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值