一. 回顾
前面学习了Day48——使用外部Servlet容器&JSP支持,今天学习外部Servlet容器SpringBoot启动原理
二. 外部Servlet容器SpringBoot启动原理
2.1 切入点分析
应用程序为jar包时,启动方式是: 执行SpringBoot主类的main方法,启动IOC容器,创建嵌入式的Servlet容器。
应用程序为war包时,启动方式是: 启动服务器(比如Tomcat),服务器启动SpringBoot应用(SpringBootServletInitializer),启动IOC容器
war包方式的原理是遵循Servlet3.0规范的,它的启动规则是:
-
服务器启动(web应用启动),创建web应用里面每一个jar包里面的ServletContainerInitializer实例
-
ServletContainerInitializer的实现放在jar包的META-INF/services文件夹下,有一个名为javax.servlet.ServletContainerInitializer,内容是ServletContainerInitializer实现类的全类名。如下:
-
还可以使用@HandlesTypes,在应用启动时加载我们感兴趣的类
2.2 启动流程
启动Tomcat
org\springframework\spring-web\5.2.6.RELEASE\spring-web-5.2.6.RELEASE.jar!\META-INF\services\javax.servlet.ServletContainerInitializer,Spring的web模块里面有这个文件javax.servlet.ServletContainerInitializer
里面有个关键方法onStartup(),如下:(注意代码中的注释)
//加载感兴趣的类
@HandlesTypes({WebApplicationInitializer.class})
public class SpringServletContainerInitializer implements ServletContainerInitializer {
...
//传入感兴趣的类,将所有标注了@HandlesTypes的类都传入到onStartup里面
public void onStartup(@Nullable 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)ReflectionUtils
.accessibleConstructor(waiClass, new Class[0])
.newInstance());//不是接口,不是抽象的
//才创建WebApplicationInitializer实例
} 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);//每一个创建出来的实例都执行自己的onStartup()方法
}
}
}
每一个WebApplicationInitializer实例都会执行自己的onStartup()方法。
WebApplicationInitializer的子类有,如下:
可以看到这个就是我们编写的SpringBootServletInitializer的子类。
相当于我们的SpringBootServletInitializer的类会被创建出来,并执行onStartup()方法
SpringBootServletInitializer里面的onStartup()方法如下:
public abstract class SpringBootServletInitializer implements WebApplicationInitializer {
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) {
}
public void contextDestroyed(ServletContextEvent event) {
try {
super.contextDestroyed(event);
} finally {
SpringBootServletInitializer.this.deregisterJdbcDrivers(event.getServletContext());
}
}
});
} else {
this.logger.debug("No ContextLoaderListener registered, as createRootApplicationContext() did not return an application context");
}
}
SpringBootServletInitializer执行onStartup()方法时会createRootApplicationContext()创建容器。
点进看看createRootApplicationContext(),如下:
protected WebApplicationContext createRootApplicationContext(ServletContext servletContext) {
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(AnnotationConfigServletWebServerApplicationContext.class);
//会调用configure方法,SpringBootServletInitializer的
//子类ServletInitializer重写这个方法,将SpringBoot的主程序传进来
builder = this.configure(builder);
builder.listeners(new ApplicationListener[]{new SpringBootServletInitializer.WebEnvironmentPropertySourceInitializer(servletContext)});
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);
}
Spring应用就会启动并创建IOC容器。
run()方法,最终会来到this.refreshContext(context)
这里,如下:
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
this.configureHeadlessProperty();
SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting();
Collection exceptionReporters;
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
this.configureIgnoreBeanInfo(environment);
Banner printedBanner = this.printBanner(environment);
context = this.createApplicationContext();
exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
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, exceptionReporters, listeners);
throw new IllegalStateException(var10);
}
try {
listeners.running(context);
return context;
} catch (Throwable var9) {
this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
throw new IllegalStateException(var9);
}
}