使用spring boot很方便,一个jar包就可以启动了,因为它里面内嵌了tomcat等服务器。
但是spring boot也提供了部署到独立服务器的方法。
如果你看文档的话,从jar转换为war包很简单,pom.xml的配置修改略去不讲。
只看source的修改,很简单,只要一个配置类,继承自SpringBootServletInitializer, 并覆盖configure方法。
- @SpringBootApplication
 - public class TestApplication extends SpringBootServletInitializer{
 - @Override
 - protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
 - return builder.sources(TestApplication .class);
 - }
 - public static void main(String[] args) {
 - SpringApplication.run(TestApplication.class, args);
 - }
 - }
 
对,你没看错,就这么简单。
但是,我觉得但凡有点儿好奇心的人都不甘于就这么用它,总会想知道为啥这样就行了?
那么我们根据调用关系来弄个究竟。
SpringBootServletInitializer.configure
<-createRootApplicationContext
<-onStartup
<-SpringServletContainerInitializer.onStartup
SpringServletContainerInitializer这个类比较特殊,实现的是interface ServletContainerInitializer,这个类的onStartup方法,是由tomcat调用了。
那么tomcat是怎么找到它的呢?是搜寻的这个资源文件META-INF/services/javax.servlet.ServletContainerInitializer
而在spring的包spring-web-xxxx.jar包里正好有这个文件,它注册的恰恰就是这个类
这个类有个注解@HandlesTypes(WebApplicationInitializer.class)。
调用SpringServletContainerInitializer.onStartup方法时,会把所有的WebApplicationInitializer类以及子类都传过来。
然后再通过条件过滤一下。
- 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);
 - }
 - }
 
也就是只要是非interface,且非抽象类,并都是WebApplicationInitializer的字类的话,就会被实例化,并最终调用。
然后,在SpringBootServletInitializer的createRootApplicationContext方法里,最终会初始化SpringApplication,调用其run方法,跟直接运行入口的main方法是一样的了。
既然从,SpringApplication.run方法以后走的逻辑是一样的,那么是不是需要启动内嵌web服务器的分支是在哪儿呢?
顺着这条线往下走。
- SpringApplication.run(String...)
 - SpringApplication.createAndRefreshContext(SpringApplicationRunListeners, ApplicationArguments)
 - SpringApplication.refresh(ApplicationContext)
 - AnnotationConfigEmbeddedWebApplicationContext(EmbeddedWebApplicationContext).refresh()
 - AnnotationConfigEmbeddedWebApplicationContext(AbstractApplicationContext).refresh()
 - AnnotationConfigEmbeddedWebApplicationContext(EmbeddedWebApplicationContext).onRefresh()
 - AnnotationConfigEmbeddedWebApplicationContext(EmbeddedWebApplicationContext).createEmbeddedServletContainer()
 
有下面一个分支代码
- if (localContainer == null && localServletContext == null) {
 - EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
 - this.embeddedServletContainer = containerFactory
 - .getEmbeddedServletContainer(getSelfInitializer());
 - }
 
localContainer在初始化的时候没有赋值过程,一直会是null,主要是localServletContext,看看什么时候为null,什么时候有值。
它的赋值有三个地方,两个构造函数,一个set方法
- public GenericWebApplicationContext(ServletContext servletContext) {
 - this.servletContext = servletContext;
 - }
 - public GenericWebApplicationContext(DefaultListableBeanFactory beanFactory, ServletContext servletContext) {
 - super(beanFactory);
 - this.servletContext = servletContext;
 - }
 - public void setServletContext(ServletContext servletContext) {
 - this.servletContext = servletContext;
 - }
 
查找一下,发现构造函数并没有地方调用,调用的是这个set方法,过程如下
- SpringApplication.run(String...)
 - SpringApplication.createAndRefreshContext(SpringApplicationRunListeners, ApplicationArguments)
 - SpringApplication.applyInitializers(ConfigurableApplicationContext)
 - ServletContextApplicationContextInitializer.initialize(ConfigurableApplicationContext)
 - ServletContextApplicationContextInitializer.initialize(ConfigurableWebApplicationContext)
 - AnnotationConfigEmbeddedWebApplicationContext(GenericWebApplicationContext).setServletContext(ServletContext)
 
你会发现,至少到SpringApplication.applyInitializers(ConfigurableApplicationContext)这一步,部署不部署到tomcat,都会执行这个方法的,那么区别在哪儿呢?
先看看这个方法的内容
- protected void applyInitializers(ConfigurableApplicationContext context) {
 - for (ApplicationContextInitializer initializer : getInitializers()) {
 - Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(
 - initializer.getClass(), ApplicationContextInitializer.class);
 - Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
 - initializer.initialize(context);
 - }
 - }
 
也就是说,如果注入的initializers里是否包含了ServletContextApplicationContextInitializer,就能决定是否会调用以后的逻辑。
那么返回到文章的开头,看看抽象类SpringBootServletInitializer,就会发现在方法createRootApplicationContext里,类ServletContextApplicationContextInitializer的注入过程。
- builder.initializers(new ServletContextApplicationContextInitializer(servletContext));
 
内部实现就是
- public SpringApplicationBuilder initializers(
 - ApplicationContextInitializer<?>... initializers) {
 - this.application.addInitializers(initializers);
 - return this;
 - }
 
至于spring的御用servlet——DispatcherServlet,则是通过动态添加方式添加到ServletContext里的。类EmbeddedWebApplicationContext
- private void selfInitialize(ServletContext servletContext) throws ServletException {
 - --------省略------------
 - for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
 - beans.onStartup(servletContext);
 - }
 - }
 
类ServletRegistrationBean
- @Override
 - public void onStartup(ServletContext servletContext) throws ServletException {
 - Assert.notNull(this.servlet, "Servlet must not be null");
 - String name = getServletName();
 - if (!isEnabled()) {
 - logger.info("Servlet " + name + " was not registered (disabled)");
 - return;
 - }
 - logger.info("Mapping servlet: ‘" + name + "‘ to " + this.urlMappings);
 - Dynamic added = servletContext.addServlet(name, this.servlet);
 - if (added == null) {
 - logger.info("Servlet " + name + " was not registered "
 - + "(possibly already registered?)");
 - return;
 - }
 - configure(added);
 - }
 
        