- 前面我们已经介绍了SpringBoot的内置Tomcat的启动过程原理
- 那么,SpringBoot打包成war包的时候,又是怎么运行的?
- 在此之前先记结论
SpringBoot程序是jar的方式,是通过IOC容器启动的方式,带动了Tomcat的启动;若SpringBoot程序是war包的方式,则是Tomcat启动带动了IOC容器的启动
如何写一个war类型的SpringBoot程序
- 首先打包的类型选择为war
- 导入的maven依赖中,排除内置的Tomcat,因为我们需要放在自己的Tomcat容器中去执行
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</dependency>
- 在主启动类中继承SpringBootServletInitializer类,并重写其configure方法,该方法的作用就是指明其主启动类
package com.xiyou.war;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
/**
* @author 92823
*/
@SpringBootApplication
public class SpringBootWarApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(SpringBootWarApplication.class, args);
}
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(SpringBootWarApplication.class);
}
}
带着问题看源码:
- 为什么我们需要在启动类上继承SpringBootServletInitializer 类?
- 我们重写的configure方法又是在什么时候触发调用的呢?
源码解析
- 首先从Servlet3.0的特性开始说起
(1)Web应用启动的时候,会创建当前Web应用导入jar包中的ServletContainerInitializer类的实例
(2)ServletContainerInitializer类必须放在jar包的META-INF/services目录下,文件名称为javax.servlet.ServletContainerInitializer
(3)文件的内容指向了ServletContainerInitializer实现类的全路径
(4)使用@HandlesTypes在我们应用启动的时候,加载我们感兴趣的类
- 观察加载的类
/*
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web;
import java.lang.reflect.Modifier;
import java.util.LinkedList;
import java.util.List;
import java.util.ServiceLoader;
import java.util.Set;
import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.HandlesTypes;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.lang.Nullable;
import org.springframework.util.ReflectionUtils;
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
@Override
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
// 保存感兴趣的类的集合
List<WebApplicationInitializer> initializers = new LinkedList<>();
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 {
// 通过反射创建这些对象
// 我们的主启动类因为继承SpringBootServletInitializer 的关系也是一个感兴趣的类
initializers.add((WebApplicationInitializer)
ReflectionUtils.accessibleConstructor(waiClass).newInstance());
}
catch (Throwable ex) {
throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
}
}
}
}
if (initializers.isEmpty()) {
servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
return;
}
servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
AnnotationAwareOrderComparator.sort(initializers);
// 循环调用集合中的感兴趣的类的对象的onstartup方法,其中我们的主启动类就会在这里调用onStartup方法
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
}
}
- 观察其onstart方法
- 因为我们的主启动类中没有onstartup方法,所以我们调用的实际是父类的onstartup方法
public abstract class SpringBootServletInitializer implements WebApplicationInitializer {
.......
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
// Logger initialization is deferred in case an ordered
// LogServletContextInitializer is being used
this.logger = LogFactory.getLog(getClass());
// 创建Web应用的上下文对象
WebApplicationContext rootAppContext = createRootApplicationContext(
servletContext);
if (rootAppContext != null) {
servletContext.addListener(new ContextLoaderListener(rootAppContext) {
@Override
public void contextInitialized(ServletContextEvent event) {
// no-op because the application context is already initialized
}
});
}
else {
this.logger.debug("No ContextLoaderListener registered, as "
+ "createRootApplicationContext() did not "
+ "return an application context");
}
}
.......
- 我们继续追踪代码,跟进WebApplicationContext rootAppContext = createRootApplicationContext(
servletContext);
protected WebApplicationContext createRootApplicationContext(
ServletContext servletContext) {
// 创建Spring的应用的构建器
SpringApplicationBuilder builder = createSpringApplicationBuilder();
builder.main(getClass());
// 设置环境
ApplicationContext parent = getExistingRootWebApplicationContext(servletContext);
if (parent != null) {
this.logger.info("Root context already created (using as parent).");
servletContext.setAttribute(
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null);
builder.initializers(new ParentContextApplicationContextInitializer(parent));
}
builder.initializers(
new ServletContextApplicationContextInitializer(servletContext));
builder.contextClass(AnnotationConfigServletWebServerApplicationContext.class);
// 注意:因为我们已经重写了configure方法,所以实际上调用的是我们的configure方法
// 此时的application已经指向了我们的主启动类了
builder = configure(builder);
builder.listeners(new WebEnvironmentPropertySourceInitializer(servletContext));
SpringApplication application = builder.build();
if (application.getAllSources().isEmpty() && AnnotationUtils
.findAnnotation(getClass(), Configuration.class) != null) {
application.addPrimarySources(Collections.singleton(getClass()));
}
Assert.state(!application.getAllSources().isEmpty(),
"No SpringApplication sources have been defined. Either override the "
+ "configure method or add an @Configuration annotation");
// Ensure error pages are registered
if (this.registerErrorPageFilter) {
application.addPrimarySources(
Collections.singleton(ErrorPageFilterConfiguration.class));
}
// 调用run方法,正式开启加载IOC
return run(application);
}
- 继续观察上面的run方法
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
// 启动我们的IOC容器
// 这个就开始IOC的加载了
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
执行到此我们已经开始加载IOC容器,我们可以明确是Tomcat的启动带动了IOC容器的启动,通过onStartUp方法逐渐引入到run方法的执行,引入到Spring IOC容器的加载
总结
- Tomcat的Host容器在添加子容器的时候,会通过类加载器加载@HandlesTypes注解的类
- 读取@HandlesTypes注解的value值,并将其放入到ServletContainerInitializers 对应的Set集合中
- ApplicationContext内部启动的时候会通知ServletContainerInitializer的onStart方法
- 通过onStart方法,执行自己实现的configure方法,指明主类,执行run方法,加载Spring的IOC