SpringBoot与Tomcat的启动(war包形式)

使用SpringBoot开发,如果要打成war包,那么工程的启动类就必须实现WebApplicationInitializer接口,如下(父类SpringBootServletInitializer实现了该接口):

@SpringBootApplication
public class Application extends SpringBootServletInitializer {
    @Override
    protected SpringApplicationBuilder configure(
                                        SpringApplicationBuilder application) {
        return application.sources(Application.class);
    }
}

之所以需要实现该接口,是因为当SpringBoot不是使用内置Tomcat启动时,那么它就需要由外部的Tomcat启动,根据Servlet 3.0协议,Tomcat容器在启动的时候会扫描工程内所有实现ServletContainerInitializer接口的类,根据这个原理,Spring提供了SpringServletContainerInitializer

// 注解@HandlesTypes表示当前需要Tomcat帮忙加载的接口实现类
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {

    @Override
    public void onStartup(Set<Class<?>> webAppInitializerClasses, 
            ServletContext servletContext) throws ServletException {

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

        // 校验Tomcat启动过程传递进来的webAppInitializerClasses实现类;
        // 收集到一个集合中
        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) {
                        // 忽略异常...
                    }
                }
            }
        }

        // ...

        // 排序
        AnnotationAwareOrderComparator.sort(initializers);
        // 执行收集方法的onStartup方法,这里会收集到工程启动类
        for (WebApplicationInitializer initializer : initializers) {
            initializer.onStartup(servletContext);
        }
    }
}

通过上面源码了解到通过servlet 3.0协议,Spring令Tomcat启动的时候回调SpringServletContainerInitializer类,并将工程内所有符合条件的WebApplicationInitializer实现类逐一启动

回到本文开头的工程启动类,它并没有#onStartup方法的实现,那么必然在父类SpringBootServletInitializer

public abstract class SpringBootServletInitializer 
            implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        // Logger initialization is deferred in case a ordered
        // LogServletContextInitializer is being used
        WebApplicationContext rootAppContext = createRootApplicationContext(
                servletContext);
        // ...
    }

    protected WebApplicationContext createRootApplicationContext(
            ServletContext servletContext) {
        SpringApplicationBuilder builder = createSpringApplicationBuilder();
        StandardServletEnvironment environment = new StandardServletEnvironment();
        environment.initPropertySources(servletContext, null);
        builder.environment(environment);
        builder.main(getClass());
        ApplicationContext parent = getExistingRootWebApplicationContext(servletContext);
        if (parent != null) {
            servletContext.setAttribute(
                    WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null);
            builder.initializers(new ParentContextApplicationContextInitializer(parent));
        }
        builder.initializers(
                new ServletContextApplicationContextInitializer(servletContext));
        builder.contextClass(AnnotationConfigEmbeddedWebApplicationContext.class);
        // 此处回调子类,即本文开头工程启动类
       // 主要获取启动类
        builder = configure(builder);
        SpringApplication application = builder.build();
        if (application.getSources().isEmpty() 
                && AnnotationUtils.findAnnotation(getClass(), Configuration.class) != null) {
            application.getSources().add(getClass());
        }
        // Ensure error pages are registered
        if (this.registerErrorPageFilter) {
            application.getSources().add(ErrorPageFilterConfiguration.class);
        }
        return run(application);
    }

    protected WebApplicationContext run(SpringApplication application) {
        // 启动Spring容器
        return (WebApplicationContext) application.run();
    }
}

接下来便是熟悉的Spring容器启动逻辑

public class SpringApplication {

    public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        FailureAnalyzers analyzers = null;
        configureHeadlessProperty();
        SpringApplicationRunListeners listeners = getRunListeners(args);
        listeners.starting();
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                    args);
            ConfigurableEnvironment environment = prepareEnvironment(listeners,
                    applicationArguments);
            Banner printedBanner = printBanner(environment);
            context = createApplicationContext();
            analyzers = new FailureAnalyzers(context);
            prepareContext(context, environment, listeners, applicationArguments,
                    printedBanner);
            refreshContext(context);
            afterRefresh(context, applicationArguments);
            listeners.finished(context, null);
            stopWatch.stop();
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass)
                        .logStarted(getApplicationLog(), stopWatch);
            }
            return context;
        } catch (Throwable ex) {
            handleRunFailure(context, listeners, analyzers, ex);
            throw new IllegalStateException(ex);
        }
    }
}

之后便是熟悉的Spring容器启动过程,可以参考前文,与之不同的是,由于当前由Tomcat驱动Spring容器启动,那么容器无须额外启动Tomcat(事实上,也不会将内置Tomcat一起打包)

public class ServletWebServerApplicationContext 
            extends GenericWebApplicationContext
            implements ConfigurableWebServerApplicationContext {

    private void createWebServer() {
        WebServer webServer = this.webServer;
        ServletContext servletContext = getServletContext();
        // servletContext 存在,不执行Tomcat的创建
        if (webServer == null && servletContext == null) {
            ServletWebServerFactory factory = getWebServerFactory();
            this.webServer = factory.getWebServer(getSelfInitializer());
        } else if (servletContext != null) {
            try {
                getSelfInitializer().onStartup(servletContext);
            } catch (ServletException ex) {
                // ...
            }
        }
        initPropertySources();
    }
}
外置Tomcat启动SpringBoot
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。