项目实战之干掉Web.xml

引言

在java Web开发过程我们早已习惯了Web.xml的繁杂的配置,我们从生产低下的”jsp+Servlet”时期进入到SSH在步入到较为先进的SSM时期,仍然没有逃脱Web.xml.令人惊喜的是Servlet3.0 带来了全新的配置方式允许我们可以使用编程的方式实现接口去进行ServletContext配置.正如Spring官方文档里说的,这和基于(或者可能是结合)传统的web.xml方法恰恰相反。我觉得应该是一个令人惊喜的改变.而后在SpringBoot给我们带来了AutoConfiguration,极大的解放了生产力.

今天我要说的不是使用SpringBoo干掉Web.xml,而是在SSM框架中干掉Web.xml,请听我细细道来.

可恨的Web.xml

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

上面就是我们 在使用SSM框架开发所要配置的Web.xml文件,是不是挺繁琐的.

现如今,YAML,Properties,JSON等文件以简洁和可读性更高的优点不禁让我们趋之若鹜,甘愿拜倒在他们的石榴裙下.当然,我并不是说XML文件应该被淘汰,它在有些地方有着不可替代的作用;而是我觉得配置文件就应该简洁,让人一眼就知道一段代码是干啥的,不需要解读标签的含义就能够全部理解.说了这么多那我们该如何替代呢?请往下看.

WebApplicationInitializer简介

查看源码得知,WebApplicationInitializer就是一个接口,其结构很简单,就包含一个方法onStartup(),结构如下:

public interface WebApplicationInitializer {

   /**
    * Configure the given {@link ServletContext} with any servlets, filters, listeners
    * context-params and attributes necessary for initializing this web application. See
    * examples {@linkplain WebApplicationInitializer above}.
    * @param servletContext the {@code ServletContext} to initialize
    * @throws ServletException if any call against the given {@code ServletContext}
    * throws a {@code ServletException}
    */
   void onStartup(ServletContext servletContext) throws ServletException;

}

所有的初始化类都实现了此接口,直接或间接实现此接口的类有:AbstractContextLoaderInitializerAbstractReactiveWebInitializer这两个抽象类,AbstractReactiveWebInitializer作用是为了方便WebApplicationInitializer 将一个ClassLoaderListener 注册到Servlet 上下文中.而AbstractReactiveWebInitializer的作用是为响应式Web 程序提供一个初始化基类.由此可见,我们使用JavaConfig模式不仅可以配置传统的Web应用,也可以很好的配置响应式Web应用.下面让我们先来看看该如何利用javaConfig和Web.xml进行.下面是对应DispatcherServlet注册逻辑, WebApplicationInitializer实现如下:

 public class MyWebAppInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext container) {
      XmlWebApplicationContext appContext = new XmlWebApplicationContext();
      appContext.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml");

      ServletRegistration.Dynamic dispatcher =
        container.addServlet("dispatcher", new DispatcherServlet(appContext));
      dispatcher.setLoadOnStartup(1);
      dispatcher.addMapping("/");
    }

 }

如果对于上述的配置有其他的考虑,也可以继承org.springframework.web.servlet.support.AbstractDispatcherServletInitializer,此类的父类就是我们上面提到的AbstractContextLoaderInitializer并在其基础上进行了扩展.继承此类Spring将会自动为你完成配置.

我们看到这种方式更简单,也更简洁.不用担心处理初始化参数,只是普通的JavaBean风格的属性和构造函数参数。

在官方文档中提到,Spring的大多数组件已经进行了更新优化,用来支持上述风格的注册工作.当你在使用Spring或者源码的时候,你会惊奇的发现DispatcherServletFrameworkServletContextLoaderListenerDelegatingFilterProxy现在都支持使用构造函数进行参数注册.现在如果我们,们使用Servlet3.0,我们完全的干掉Web.xml,用代码将ServletContext API的进行实现和扩展,以配置init-paramscontext-params 等必要的参数.

既然有了这么多的支持,我们又该如何完美且不失优雅的干掉Web.xml呢?请看代码:

public class MyWebAppInitializer implements WebApplicationInitializer { 
@Override
  public void onStartup(ServletContext container) {
    // Create the 'root' Spring application context
    AnnotationConfigWebApplicationContext rootContext =
      new AnnotationConfigWebApplicationContext();
    rootContext.register(AppConfig.class);
  
    // Manage the lifecycle of the root application context
    container.addListener(new ContextLoaderListener(rootContext));
  
    // Create the dispatcher servlet's Spring application context
    AnnotationConfigWebApplicationContext dispatcherContext =
      new AnnotationConfigWebApplicationContext();
    dispatcherContext.register(DispatcherConfig.class);
  
    // Register and map the dispatcher servlet
    ServletRegistration.Dynamic dispatcher =
      container.addServlet("dispatcher", new DispatcherServlet(dispatcherContext));
    dispatcher.setLoadOnStartup(1);
    dispatcher.addMapping("/");
  }
  }

同样你也可以不直接实现WebApplicationInitializer 而使用继承org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer的 方式去配置你的应用程序 .这所有的操做都是由spring自动发现并配置----所以你可以用你自己独特的方式去开发Spring应用,不需要过多的考虑其他的事情.

更为神奇的是,Spring为我们提供了一种机制以确保确保Servlet容器的初始化顺序.因为我们可以选择实现此接口的同时选择使用@Order或者是实现Order接口,但是会造成这样初始化在调用前进行.虽然这种方式在典型的Web应用中很少使用.但是Spring也为我们考虑到了.

讨论

Web.xml并不是和WebApplicationInitializer相互排斥,恰恰相反他们可以协同工作,例如,在web.xml中可以注册一个servlet和在WebApplicationInitializer可以注册另一个。 一个初始化甚至可以在web.xml通过方法修改改变注册行为,如ServletContext.getServletRegistration(String) 。 但是如果WEB-INF/web.xml存在于应用程序,它的version属性必须被设置为“3.0”或更大,否则ServletContainerInitializer的引导将会被servlet容器忽略。

上面我说到三种常见配置文件可以代替Web.xml,基于上面javaConfig思路,我们可以自定义配置文件,将那些未来可能会发生变动放到配置文件(这里的文件特指:YAML,Properties,JSON中的任意一个)里,那些不会发生变动或者变动很少,使用java硬编码的方式以代码的方式进行配置.

说明

本文章为作者读书笔记及感悟,其中参考了《spring5核心原理与30个类手写实战》以及互联网上的内容。如有错误,请评论或者私聊我,欢迎探讨技术问题 。即将毕业,在准备找工作,有朋友想给我介绍的,欢迎添加微信:sllbiao。

上一篇:Servlet入门和ServletConfig、ServletContext


下一篇:Java大数据之路--ServletContext