通过 new -> project 新的spring工程,选中使用war包打包方式,构建成的项目可以使用tomcat外部部署并加载,通过日志打印,能发现,是先加载了servlet容器,再加载spirngboot应用。

我们建好的war包的打包方式的项目目录结构如下:

与原来jar包打包方式的项目,多了一个ServletInitializer类,还有我们熟悉的webapp与WEB-INF文件夹。

做一个比较:

jar包形式 : 执行SpringBoot主类的main方法,启动ioc容器,创建嵌入式的Servlet容器。
web包形式 : 启动服务器,服务器启动SpringBoot应用【SpringBootServletInitializer】,启动ioc容器。

前言引入

在 servlet3.0 中有一项规范,在8.2.4章节,文档内容如下

规范内容

1.服务器启动(web应用启动)会创建当前web应用里面每一个jar包里面ServletContainerInitializer实例:
2.ServletContainerInitializer的实现放在jar包的META-INF/services文件夹下,有一个名为 javax.servlet.ServletContainerInitializer的文件,内容就是ServletContainerInitializer的实现类的全类名
3.还可以使用**@HandlesTypes**,在应用启动的时候加载我们感兴趣的类;

我们查看spring-web中,有一个META-INF/services/javax.servlet.ServletContainerInitializer

里面的内容只要一条org.springframework.web.SpringServletContainerInitializer,它是要创建的类。

启动流程

1.启动tomcat

2.在web模块中这个文件,spring-web/META-INF/services/javax.servlet.ServletContainerInitializer,点进去查看该类

// 将感兴趣的类 传入这个注解中
@HandlesTypes({
   WebApplicationInitializer.class})
public class SpringServletContainerInitializer implements ServletContainerInitializer {
   
    public SpringServletContainerInitializer() {
   
    }
	
	// 调用onStartup方法
	
	// 第一个Set<Class<?>> webAppInitializerClasses参数
	//就是传入的WebApplicationInitializer.class所有的实现类
    public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException {
   
        List<WebApplicationInitializer> initializers = Collections.emptyList();
        Iterator var4;
        if (webAppInitializerClasses != null) {
   
            initializers = new ArrayList(webAppInitializerClasses.size());
            var4 = webAppInitializerClasses.iterator();

            while(var4.hasNext()) {
   
                Class<?> waiClass = (Class)var4.next();
                // 将不是接口的,不是抽象类的实现类 通过.newInstance()实例化
                if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
   
                    try {
   
                        ((List)initializers).add((WebApplicationInitializer)ReflectionUtils.accessibleConstructor(waiClass, new Class[0]).newInstance());
                    } catch (Throwable var7) {
   
                        throw new ServletException("Failed to instantiate WebApplicationInitializer class", var7);
                    }
                }
            }
        }

        if (((List)initializers).isEmpty()) {
   
            servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
        } else {
   
            servletContext.log(((List)initializers).size() + " Spring WebApplicationInitializers detected on classpath");
            AnnotationAwareOrderComparator.sort((List)initializers);
            var4 = ((List)initializers).iterator();
			// 然后调用它们自己的onStartup方法
            while(var4.hasNext()) {
   
                WebApplicationInitializer initializer = (WebApplicationInitializer)var4.next();
                initializer.onStartup(servletContext);
            }

        }
    }
}

里面的执行流程就是 : SpringServletContainerInitializer将**@HandlesTypes(WebApplicationInitializer.class)**标注的所有这个类型 的类都传入到onStartup方法的Set>,通过一系列判断,将它的实现类使用newInstance方法,为这些WebApplicationInitializer类型的类创建实例对象。

每一个WebApplicationInitializer都调用自己的onStartup,查看它的实现类发现

所以,这相当于我们的SpringBootServletInitializer的类(即 我们创建项目时自动生成的ServletInitializer类)会被创建对象,并执行onStartup()方法。

我们查看SpringBootServletInitializer类,它在执行onStartup的时候会调用createRootApplicationContext方法创建容器。

	public void onStartup(ServletContext servletContext) throws ServletException {
   
        this.logger = LogFactory.getLog(this.getClass());
        // 在调用onStartup方法时,调用createRootApplicationContext方法,在下面
        WebApplicationContext rootApplicationContext = this.createRootApplicationContext(servletContext);
        if (rootApplicationContext != null) {
   
            servletContext.addListener(new SpringBootServletInitializer.SpringBootContextLoaderListener(rootApplicationContext, servletContext));
        } else {
   
            this.logger.debug("No ContextLoaderListener registered, as createRootApplicationContext() did not return an application context");
        }
    }

	protected WebApplicationContext createRootApplicationContext(ServletContext servletContext) {
   
		//创建SpringApplicationBuilder spring应用的构建器
        SpringApplicationBuilder builder = this.createSpringApplicationBuilder();
        builder.main(this.getClass());
        ApplicationContext parent = this.getExistingRootWebApplicationContext(servletContext);
        if (parent != null) {
   
            this.logger.info("Root context already created (using as parent).");
            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, (Object)null);
            builder.initializers(new ApplicationContextInitializer[]{
   new ParentContextApplicationContextInitializer(parent)});
        }

        builder.initializers(new ApplicationContextInitializer[]{
   new ServletContextApplicationContextInitializer(servletContext)});
        builder.contextFactory((webApplicationType) -> {
   
            return new AnnotationConfigServletWebServerApplicationContext();
        });
        // 调用configure方法,子类重写了这个方法,将SpringBoot的主程序类传入了进来,见下面的方法
        builder = this.configure(builder);
        builder.listeners(new ApplicationListener[]{
   new SpringBootServletInitializer.WebEnvironmentPropertySourceInitializer(servletContext)});
        // 使用builder创建一个Spring应用
        SpringApplication application = builder.build();
        if (application.getAllSources().isEmpty() && MergedAnnotations.from(this.getClass(), SearchStrategy.TYPE_HIERARCHY).isPresent(Configuration.class)) {
   
            application.addPrimarySources(Collections.singleton(this.getClass()));
        }

        Assert.state(!application.getAllSources().isEmpty(), "No SpringApplication sources have been defined. Either override the configure method or add an @Configuration annotation");
        if (this.registerErrorPageFilter) {
   
            application.addPrimarySources(Collections.singleton(ErrorPageFilterConfiguration.class));
        }

        application.setRegisterShutdownHook(false);
        //启动Spring应用
        return this.run(application);
    }

    protected SpringApplicationBuilder createSpringApplicationBuilder() {
   
        return new SpringApplicationBuilder(new Class[0]);
    }

    protected WebApplicationContext run(SpringApplication application) {
   
        return (WebApplicationContext)application.run(new String[0]);
    }
@Override
	// 子类重写configure方法,传入我们自己的springboot主程序
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
   
        return application.sources(SpringBootApplication.class);
    }

最后在run方法中,启动spirngboot应用并且创建IOC容器

public ConfigurableApplicationContext run(String... args) {
   
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        DefaultBootstrapContext bootstrapContext = this.createBootstrapContext();
        ConfigurableApplicationContext context = null;
        this.configureHeadlessProperty();
        SpringApplicationRunListeners listeners = this.getRunListeners(args);
        listeners.starting(bootstrapContext, this.mainApplicationClass);

        try {
   
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);
            this.configureIgnoreBeanInfo(environment);
            Banner printedBanner = this.printBanner(environment);
            context = this.createApplicationContext();
            context.setApplicationStartup(this.applicationStartup);
            this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
            
            //刷新IOC容器
            this.refreshContext(context);
            this.afterRefresh(context, applicationArguments);
            stopWatch.stop();
            if (this.logStartupInfo) {
   
                (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
            }

            listeners.started(context);
            this.callRunners(context, applicationArguments);
        } catch (Throwable var10) {
   
            this.handleRunFailure(context, var10, listeners);
            throw new IllegalStateException(var10);
        }

        try {
   
            listeners.running(context);
            return context;
        } catch (Throwable var9) {
   
            this.handleRunFailure(context, var9, (SpringApplicationRunListeners)null);
            throw new IllegalStateException(var9);
        }
    }

所以,外部启动servlet容器启动springboot,就是先启动Servlet容器,再启动SpringBoot应用。