springMVC WebApplicationInitializer 替代web.xml 配置Servlet 之原理
阅读原文时间:2023年07月08日阅读:3

Servlet 3.0之前 ,xml  配置

在过去搭建spring + springMCV ,首先第一步要做的是什么 ,就是要配置web.xml 文件 ,把springMVC 中的Servlet 加载到tomcat 。通过加载 dispatcher 来启动整个spring容器。web.xml 如下 。

dispatcher org.springframework.web.servlet.DispatcherServlet contextConfigLocation /WEB-INF/spring/dispatcher-config.xml 1

dispatcher /

Servlet 3.0后 ,java 配置

在Servlet 进入在3.0+时代后,Servlet 变支持了注解配置Servlet,而spring也推荐 java 代码的配置 。那么以上web.xml 配置Servlet,便可以通过实现WebApplicationInitializer 接口,来完成Servlet的配置 ,直接看代码,如下

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration.Dynamic;

import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;

public class WebInitializer implements WebApplicationInitializer {// 1 实现接口
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
ctx.register(MyMvcConfig.class); //注册自己的配置文件
ctx.setServletContext(servletContext); // 2 把 ServletContext 注入到spring容器中
Dynamic servlet = servletContext.addServlet("dispatcher", new DispatcherServlet(ctx)); // 3 加载 dispatcher Servlet。
servlet.addMapping("/");
servlet.setLoadOnStartup(1);
}
}

MyMvcConfig 为自己的配置java文件 。,这里做简单的为容器注入了试图解析器。

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.JstlView;

@Configuration // 配置文件的支持
@EnableWebMvc // 开启springMCV
@ComponentScan("xxx") //扫描加载java文件
public class MyMvcConfig {
@Bean
public InternalResourceViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/classes/views/");
viewResolver.setSuffix(".jsp");
viewResolver.setViewClass(JstlView.class);
return viewResolver;
}
}

ok 其实这样配置,部署到tomcat中 ,运行,就会启动 WebInitializer.onStartup 来启动spring中的各种组件 。

原理分析

在第一次接触到的时候还是和困惑?这个tomcat 是怎么时候,怎么来运行 WebInitializer.onStartup这个方法的呢。?一跟踪代码方法spring两个jar包中有这个代码的引用 ,一个是支持jetty的一个,web jar包 ,
如下图,这里就只跟踪下web中的代码 ,对jetty就不研究了

点进 SpringServletContainerInitializer 这个类,发现这个类 继承实现了 ServletContainerInitializer 这个接口 ,路径:javax.servlet.ServletContainerInitializer 。这个类可不是spring中的东西了 。完整代码 :

package org.springframework.web;

import java.lang.reflect.Modifier;
import java.util.LinkedList;
import java.util.List;
import java.util.ServiceLoader;
import java.util.Set;
import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.HandlesTypes;

import org.springframework.core.annotation.AnnotationAwareOrderComparator;

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

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

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

    AnnotationAwareOrderComparator.sort(initializers);  
    servletContext.log("Spring WebApplicationInitializers detected on classpath: " + initializers);

    for (WebApplicationInitializer initializer : initializers) {  
        initializer.onStartup(servletContext);  
    }  
}

}

注意标记黄色部分,一看原来不就是,把所有 WebApplicationInitializer 遍历了一遍,然后调用了 onStartup 方法把 servletContext传进去吗。 而我们开始写的 WebInitializer就属于

WebApplicationInitializer ,所以我们的WebInitializer从这里开始运行。那么 ServletContainerInitializer 又 是怎么加载的? 这个就要跟tomcat那边的代码了,我猜,也是拿到 所有 ServletContainerInitializer 的实现类,运行了 onStartup方法 。