SpringBoot——外置Servlet容器启动SpringBoot应用的原理

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/rubulai/article/details/84037441

一、内置Servlet容器和外置Servlet容器的对比
  内置:将应用打成jar包,项目启动时执行SpringBoot主配置类的main方法,启动IOC容器,创建嵌入式的Servlet容器并启动
  外置:将应用打成war包,先启动外置的Servlet服务器(如tomcat),通过外置的服务器启动SpringBoot应用(将SpringBoot应用的主配置类作为参数传入SpringBootServletInitializer的实现类的configure方法中),再启动IOC容器

public class ServletInitializer extends SpringBootServletInitializer {
    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
    	//SpringbootWarApplication是该应用的主配置类
        return application.sources(SpringbootWarApplication.class);
    }
}

二、原理
  在Servlet3.0规范中有一个规范:服务器启动(web应用启动)时会创建当前web应用里面每一个jar包下的ServletContainerInitializer接口的实例,每个jar包下该接口的实现类会在该jar包的META-INF/services文件夹下名为javax.servlet.ServletContainerInitializer的文件中指定,该文件中指定的就是ServletContainerInitializer接口的实现类的全类名。在该实现类中还可以使用@HandlesTypes加载我们感兴趣的类,把我们感兴趣的类在加入到容器中。
  而在SpringBoot的web模块下就有一个javax.servlet.ServletContainerInitializer文件:
在这里插入图片描述
 该文件中指定的实现类的源码如下:

@HandlesTypes({WebApplicationInitializer.class})
public class SpringServletContainerInitializer implements ServletContainerInitializer {
    public SpringServletContainerInitializer() {
    }

    public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException {
        List<WebApplicationInitializer> initializers = new LinkedList();
        Iterator var4;
        if (webAppInitializerClasses != null) {
            var4 = webAppInitializerClasses.iterator();

            while(var4.hasNext()) {
                Class<?> waiClass = (Class)var4.next();
                if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
                    try {
                        initializers.add((WebApplicationInitializer)waiClass.newInstance());
                    } catch (Throwable var7) {
                        throw new ServletException("Failed to instantiate WebApplicationInitializer class", var7);
                    }
                }
            }
        }

        if (initializers.isEmpty()) {
            servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
        } else {
            servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
            AnnotationAwareOrderComparator.sort(initializers);
            var4 = initializers.iterator();

            while(var4.hasNext()) {
                WebApplicationInitializer initializer = (WebApplicationInitializer)var4.next();
                initializer.onStartup(servletContext);
            }

        }
    }
}

  在这段代码的第一行就使用@HandlesTypes将WebApplicationInitializer.class作为我们感兴趣的类引入了,我们可以看到在最后会调用该类的onStartup(),而我们在创建SpringBoot项目选择将其打成war时在项目中会有一个继承于SpringBootServletInitializer的类,SpringBootServletInitializer其实是WebApplicationInitializer的子类,这样的话在外置的Servlet容器启动时就会运行SpringBootServletInitializer的子类的onStartup(),在SpringBootServletInitializer的子类的configure()中我们又传入了SpringBoot应用的主配置类,如此就可以在启动Servlet容器时运行SpringBoot主配置类的main(),从而启动SpringBoot应用了。
三、流程
 1、启动Tomcat
 2、在web模块下的\META-INF\services\javax.servlet.ServletContainerInitializer文件中扫描到ServletContainerInitializer的实现类org.springframework.web.SpringServletContainerInitializer
 3、SpringServletContainerInitializer将@HandlesTypes(WebApplicationInitializer.class)标注的所有WebApplicationInitializer类型的类都传入到onStartup方法的Set中,为WebApplicationInitializer类型所有类创建实例,而SpringBootServletInitializer也是WebApplicationInitializer的一个子类

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

在这里插入图片描述
 4、每一个WebApplicationInitializer的实现类都会调用自己的onStartup(),相当于我们的SpringBootServletInitializer的类会被创建对象,并执行onStartup方法
 5、SpringBootServletInitializer实例执行onStartup的时候会调用createRootApplicationContext()创建容器:

public void onStartup(ServletContext servletContext) throws ServletException {
	this.logger = LogFactory.getLog(this.getClass());
	//创建容器
	WebApplicationContext rootAppContext = this.createRootApplicationContext(servletContext);
	if (rootAppContext != null) {
		servletContext.addListener(new ContextLoaderListener(rootAppContext) {
			public void contextInitialized(ServletContextEvent event) {
			}
		});
	} else {
		this.logger.debug("No ContextLoaderListener registered, as createRootApplicationContext() did not return an application context");
	}

}

 在创建容器的时候会启动SpringBoot应用:

protected WebApplicationContext createRootApplicationContext(ServletContext servletContext) {
	// 创建SpringApplicationBuilder
	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.contextClass(AnnotationConfigEmbeddedWebApplicationContext.class);
	// 调用configure方法,子类重写了这个方法,将SpringBoot的主程序类传入
	builder = this.configure(builder);
	builder.listeners(new ApplicationListener[]{new SpringBootServletInitializer.WebEnvironmentPropertySourceInitializer(servletContext)});
	// 使用builder创建一个Spring应用
	SpringApplication application = builder.build();
	if (application.getSources().isEmpty() && AnnotationUtils.findAnnotation(this.getClass(), Configuration.class) != null) {
		application.getSources().add(this.getClass());
	}

	Assert.state(!application.getSources().isEmpty(), "No SpringApplication sources have been defined. Either override the configure method or add an @Configuration annotation");
	if (this.registerErrorPageFilter) {
		application.getSources().add(ErrorPageFilterConfiguration.class);
	}
	//启动Spring应用
	return this.run(application);
}

 6、Spring应用启动就会创建IOC容器,接着就会执行刷新容器等操作

public ConfigurableApplicationContext run(String... args) {
	StopWatch stopWatch = new StopWatch();
	stopWatch.start();
	ConfigurableApplicationContext context = null;
	FailureAnalyzers analyzers = null;
	this.configureHeadlessProperty();
	SpringApplicationRunListeners listeners = this.getRunListeners(args);
	listeners.starting();

	try {
		ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
		ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
		Banner printedBanner = this.printBanner(environment);
		context = this.createApplicationContext();
		new FailureAnalyzers(context);
		this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
		this.refreshContext(context);
		this.afterRefresh(context, applicationArguments);
		listeners.finished(context, (Throwable)null);
		stopWatch.stop();
		if (this.logStartupInfo) {
			(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
		}

		return context;
	} catch (Throwable var9) {
		this.handleRunFailure(context, listeners, (FailureAnalyzers)analyzers, var9);
		throw new IllegalStateException(var9);
	}
}

  总之在Servlet容器外置时是先启动的外置Servlet容器,再通过外置Servlet容器启动SpringBoot应用
  另外根据原理可知在我们手动创建一个打包方式为war的SpringBoot项目并使用外置的tomcat启动时只需要创建一个SpringBootServletInitializer的子类即可

猜你喜欢

转载自blog.csdn.net/rubulai/article/details/84037441