不想用web.xml了怎么办

286 阅读3分钟

前面我们已经知道了servlet3.0之后给我们留下的两个扩展ServletContainerInitializerServletContextListener,而且在上一篇内容里面我们已经看到过springmvc为了加载根容器,已经使用过ServletContextListener了,那现在来看一看ServletContainerInitializer有没有被使用。
SpringServletContainerInitializer是目前唯一的实现,而且在第一篇内容里面就已经讲过了他的具体用法,果然springmvc用了

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
   @Override
   public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
         throws ServletException {

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

      if (webAppInitializerClasses != null) {
         for (Class<?> waiClass : webAppInitializerClasses) {
ners 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) {
         initializer.onStartup(servletContext);
      }
   }

}

看看他传进来的class都有些啥,看实现一共有4个类,其中AbstractReactiveWebInitializer是响应式编程需要用到的,我们现在不关心,剩下另外三个抽象类都会被传进来,但是在onstartup方法里面明确说明了不会执行抽象类里面的onstartup方法,哪这个实现还有什么意义呢,我们去看一下。首先是AbstractContextLoaderInitializer,他在onstartup方法里面首先调用了一个创建Root容器的方法,只不过这个方法是在子类AbstractAnnotationConfigDispatcherServletInitializer里面实现的,如果创建成功了,就会去调用ContextLoaderListener的有参构造方法,ContextLoaderListener是我们上一篇才讲过的,大家应该不会陌生,只不过上一篇那个地方是通过默认的无参构造创建的,然后里面那个WebApplicationContext也就是root容器自然就是null,所以会默认帮我们创建一个xmlWebApplicationContext类型的容器,但是这里主动穿了一个容器进去,所以那里面的逻辑就会改变了

if (this.context == null) {
   this.context = createWebApplicationContext(servletContext);
}

这一步就会直接跳过了,取而代之就是这里传进去的WebApplicationContext,而且我们知道ServletContainerInitializer的方法是先于ServletContextListener执行的,所以后续还是会执行到这个initWebApplicationContext里面来,而此时里面的root容器已经有值了,所以再回过头来看这个方法为什么叫createRootApplicationContext,想必大家已经懂了吧。

然后现在就该来看一看这个子类AbstractAnnotationConfigDispatcherServletInitializer是如何创建root的了,然后发现它其实也没干啥,调用了一个未实现的方法,那不用想,这个方法肯定是有我们自己来实现的,所以意思就是我们可以实现这个方法来创建我们自己的父容器,通过代码的方式,那样就不需要spring-config.xml了,那还有个springmvc-config.xml呢,这个嘛,不要慌,看AbstractDispatcherServletInitializer这个类。他重写了onstart方法,就多做了一步注册DispatcherServlet,同时注意到这几个类的继承关系会发现,只要我们继承了最下面这个抽象类AbstractAnnotationConfigDispatcherServletInitializer,那么onstartup就会被调用,这样一来,springmvc-config.xml也不需要了。而且由于有下面两行代码

ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
listener.setContextInitializers(getRootApplicationContextInitializers());
servletContext.addListener(listener);

通过动态注册的方式,我们连web.xml都不需要了,因为之前他存在的意义就只是为了声明一个ContextLoaderListener。到这里知道该怎么办了吧,所有xml统统不要了。 下面就来实现一下

/**
 * 扫描 web子容器的bean
 */
@Configuration
@ComponentScan(basePackages = "me.ppx.mvc.springmvc.controller")
public class WebMvcConfig {
}
/**
 * 扫描root容器中的bean
 */
@Configuration
@ComponentScan(basePackages = "me.ppx.mvc.springmvc.service")
public class RootConfig {
}
package me.ppx.mvc.springmvc.config;

import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

/**
 * 通过继承AbstractAnnotationConfigDispatcherServletInitializer来注册Spring root 容器
 * 同时在父类 AbstractDispatcherServletInitializer中自动又注册了DispatcherServlet
 * 而且还注册了 ContextLoaderListener
 * 所以到这里可以抛弃所有XMl配置文件
 *
 * 该class会自动被WebApplicationInitializer扫描
 */
public class WithOutXml extends AbstractAnnotationConfigDispatcherServletInitializer {
    /**
     * 创建 root 容器 扫描除开controller的bean
     *
     * @return
     */
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{RootConfig.class};
    }

    /**
     * 创建 web子容器 扫描controller
     *
     * @return
     */
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{WebMvcConfig.class};
    }

    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
}
package me.ppx.mvc.springmvc.controller;

import me.ppx.mvc.springmvc.service.TestService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.PostConstruct;

@RestController
@RequestMapping("/mvc")
public class Controller {
    @Autowired
    private TestService service;

    @PostConstruct
    public void init() {
        System.out.println(service.getDate());
    }

    @GetMapping("/date")
    public String getDate() {
        System.out.println("通过mvc请求到controller");
        return service.getDate();
    }

}

只需要继承AbstractAnnotationConfigDispatcherServletInitializer并重写两个提供容器的方法,这样一个抛弃web.xml的基本的web服务就完成了