通过 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应用。