Spring Boot容器另类的初始化过程
- 对于Spring Boot应用来说,它并未使用SpringServletContainerInitializer来进行容器的初始化,而是使用了TomcatStarter进行的。
- TomcatStarter存在三点因素使得它无法通过SPI机制进行初始化
- 它没有不带参数的构造方法
- 它的声明并非public
- 其所在的jar包并没有META-INF/services目录
- 所以TomcatStarter并非通过SPI机制进行的查找与实例化
- 本质上,TomcatStarter是通过Spring Boot框架new出来的
- 与SpringServletContainerInitializer类似,TomcatStarter在容器的初始化过程中也是扮演一个委托或是代理的角色,真正执行的初始化动作实际上是由它所持有的ServletContextInitializer的onStartup方法。
具体代码
- ServletContextInitializer
package org.springframework.boot.web.servlet; import javax.servlet.ServletContext; import javax.servlet.ServletException; import org.springframework.web.SpringServletContainerInitializer; import org.springframework.web.WebApplicationInitializer; /** * Interface used to configure a Servlet 3.0+ {@link ServletContext context} * programmatically. Unlike {@link WebApplicationInitializer}, classes that implement this * interface (and do not implement {@link WebApplicationInitializer}) will <b>not</b> be * detected by {@link SpringServletContainerInitializer} and hence will not be * automatically bootstrapped by the Servlet container. * <p> * This interface is primarily designed to allow {@link ServletContextInitializer}s to be * managed by Spring and not the Servlet container. * <p> * For configuration examples see {@link WebApplicationInitializer}. * * @author Phillip Webb * @since 1.4.0 * @see WebApplicationInitializer */ @FunctionalInterface public interface ServletContextInitializer { /** * Configure the given {@link ServletContext} with any servlets, filters, listeners * context-params and attributes necessary for initialization. * @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; }
- TomcatStarter
/** * {@link ServletContainerInitializer} used to trigger {@link ServletContextInitializer * ServletContextInitializers} and track startup errors. * * @author Phillip Webb * @author Andy Wilkinson */ class TomcatStarter implements ServletContainerInitializer { private static final Log logger = LogFactory.getLog(TomcatStarter.class); private final ServletContextInitializer[] initializers; private volatile Exception startUpException; TomcatStarter(ServletContextInitializer[] initializers) { this.initializers = initializers; } @Override public void onStartup(Set<Class<?>> classes, ServletContext servletContext) throws ServletException { try { for (ServletContextInitializer initializer : this.initializers) { initializer.onStartup(servletContext); } } catch (Exception ex) { this.startUpException = ex; // Prevent Tomcat from logging and re-throwing when we know we can // deal with it in the main thread, but log for information here. if (logger.isErrorEnabled()) { logger.error("Error starting Tomcat context. Exception: " + ex.getClass().getName() + ". Message: " + ex.getMessage()); } } } Exception getStartUpException() { return this.startUpException; } }
- 最终会通过TomcatStarter这个委托者,获取到所有的ServletContextInitializer实例数组,然后在onStartup方法中循环调用onStartup完成初始化的配置。
和传统Spring MVC之间的对应关系
- SpringServletContainerInitializer对应于TomcatStarter
- WebApplicationInitializer对应于ServletContextInitializer
为什么不统一
当我们运行启动命令java -jar
的时候是不会调用javax.servlet.ServletContainerInitializer
这个接口的