springboot是如何在没有web.xml的情况下在外部tomcat启动的
最近在公司部署测试环境的时候遇到了问题,本地用springboot内嵌tomcat启动没有问题,部署到测试环境的外部tomcat就不加载,在咨询了同事之后在启动类上继承了SpringBootServletInitializer之后就没有了问题,但并没有深入去想。由于servlet的规范是要配置web.xml的,包括Struts2和spring都是要在web.xml配置的,有一天在服务器上看编译后的目标文件中居然没有web.xml,结合之前遇到的问题,就想知道springboot应用是如何在没有web.xml的情况下在tomcat启动的,于是就有了如下一篇文章。
概述
web.xml的模式文件是由Sun公司定义的,要用来配置Filter、Listener、Servlet等,先来回顾Spring Mvc的配置
<servlet><servlet-name>spring</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><init-param><param-name>contextConfigLocation</param-name><param-value>classpath:spring-servlet.xml</param-value></init-param><load-on-startup>1</load-on-startup>
</servlet>
?
<servlet-mapping><servlet-name>spring</servlet-name><url-pattern>/*</url-pattern>
</servlet-mapping>
?
<listener><listenerclass>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
?
<context-param><param-name>contextConfigLocation</param-name><param-value>classpath:config/applicationContext.xml</param-value>
</context-param>
Servlet3.0
web.xml的消失不得不提到servlet3.0规范的推出,通过Java配置和注解简化了web.xml的配置。
其中最关键的是定义了javax.servlet.ServletContainerInitializer这个接口,这个接口的实现类会在web容器启动阶段被回调,在onStartUp方法里对servlet、filter和listener进行一些注册的操作来代替web.xml。
/*** @since Servlet 3.0*/
public interface ServletContainerInitializer {void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException;
}
但是这个接口是Servlet定义的,Tomcat做的最常用的Servlet容器能知道这个接口,但是它怎么能找到具体的实现类来启动springboot呢,这就不得不提到SPI(Service Provider Interface)
SPI
找到spring-web包下面的META-INF\services文件夹,里面有个名字javax.servlet.ServletContainerInitializer的文件,打开文件里面只有一行,那就是spring对这个接口的实现类的全限定名,找到SpringServletContainerInitializer这个类。SpringServletContainerInitializer作为ServletContainerInitializer的实现类,通过SPI机制,在web容器加载的时候会自动的被调用。而这个类的方法又会扫描找到WebApplicationInitializer的实现类,调用它的onStartup方法,从而起到启动web.xml相同的作用。
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
?@Overridepublic void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)throws ServletException {
?List<WebApplicationInitializer> initializers = new LinkedList<WebApplicationInitializer>();
?initializers.add((WebApplicationInitializer) waiClass.newInstance());
?AnnotationAwareOrderComparator.sort(initializers);for (WebApplicationInitializer initializer : initializers) {initializer.onStartup(servletContext);}}
?
}
这个类有个一个注解@HandlesTypes,这个注解是Servlet3.0提出来的,这个注解使用了Byte Code Engineering Library (BCEL),这是Apache Software Foundation 的Jakarta 项目的一部分,作用同ASM类似,是字节码操纵框架,使用BCEL的ClassParser在字节码层面读取了/WEB-INF/classes和某些jar(应该可以在叫做fragments的概念中指定)中class文件的超类名和实现的接口名,判断是否与记录的注解类名相同。
DispatcherServlet
springboot启动后依旧需要像spring mvc一样初始化一个DispatcherServlet来从tomcat接收并分发请求
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
1)当类路径下存在DispatcherServlet时候,该配置生效。 2)这个配置会在DispatcherServletAutoConfiguration配置完之后再配置。