概述
上次一朋友面试问到了SpringBoot中内置Tomcat的启动流程,在此我也记录下,加深记忆。
在SpringBoot中引入spring-boot-starter-web依赖,这个依赖里面又引入了spring-boot-starter-tomcat依赖,这样我们就可以直接使用Tomcat服务器,都不用配置。对于嵌入式Tomcat,其实也比较简单,就是调用Tomcat提供的外部类 org.apache.catalina.startup.Tomcat 就可以实现。
启动流程
SpringBoot内置的Tomcat启动要从SpringApplication类的run()方法说起,一路点下去,来到run()方法中,run方法返回的是一个ConfigurableApplicationContext 对象,此对象就是JavaWeb的ServletContext对象。
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); // banner图案
context = this.createApplicationContext(); // 创建context对象
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();
// 略部分代码
try {
listeners.running(context);
return context;
} catch (Throwable var9) {
this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);
throw new IllegalStateException(var9);
}
}
在run()方法中又一个名为refreshContext()方法,点进去一看
private void refreshContext(ConfigurableApplicationContext context) {
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
} catch (AccessControlException var3) {
}
}
this.refresh(context);
}
再该方法中又调用了refresh()方法,点进去一看
protected void refresh(ApplicationContext applicationContext) {
Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
((AbstractApplicationContext)applicationContext).refresh();
}
再点击refresh()方法,来到AbstractApplicationContext()抽象类中的非抽象方法refresh(),这个方法中可以说是定义了Tomcat的启动流程。其中较为关键的就是onRefresh()方法,。
public void refresh() throws BeansException, IllegalStateException {
synchronized(this.startupShutdownMonitor) {
this.prepareRefresh();
ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
this.prepareBeanFactory(beanFactory);
try {
this.postProcessBeanFactory(beanFactory);
this.invokeBeanFactoryPostProcessors(beanFactory);
this.registerBeanPostProcessors(beanFactory);
this.initMessageSource();
this.initApplicationEventMulticaster();
this.onRefresh();
this.registerListeners();
this.finishBeanFactoryInitialization(beanFactory);
this.finishRefresh();
} catch (BeansException var9) {
if (this.logger.isWarnEnabled()) {
this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
}
this.destroyBeans();
this.cancelRefresh(var9);
throw var9;
} finally {
this.resetCommonCaches();
}
}
}
再单击 onRefresh()方法,发现没有写任何代码,那么肯定就是留给子类去重写,
protected void onRefresh() throws BeansException {
}
可以看到有五个类继承了AbstractApplicationContext,我们需要找的是ServletWebServerApplicationContext类,找到里面重写的 onRefresh()方法
protected void onRefresh() {
super.onRefresh();
try {
this.createWebServer();
} catch (Throwable var2) {
throw new ApplicationContextException("Unable to start web server", var2);
}
}
可以看到,在ServletWebServerApplicationContext类的onRefresh()方法中调用了 createWebServer()方法,点进去一看。
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = this.getServletContext();
if (webServer == null && servletContext == null) {
ServletWebServerFactory factory = this.getWebServerFactory();
this.webServer = factory.getWebServer(new ServletContextInitializer[]{this.getSelfInitializer()});
} else if (servletContext != null) {
try {
this.getSelfInitializer().onStartup(servletContext);
} catch (ServletException var4) {
throw new ApplicationContextException("Cannot initialize servlet context", var4);
}
}
可以看到,这里先获取到 ServletWebServerr工厂,然后再根据这个工厂获取具体的webServer。
getWebServer是接口中的方法,这个ServletWebServerFactory接口有4个实现类,这里获取到的工厂是TomcatServletWebServerFacotry这个工厂。
然后点进getWebServer()方法一看,这个方法是创建Tomcat的一些核心组件。
public WebServer getWebServer(ServletContextInitializer... initializers) {
if (this.disableMBeanRegistry) {
Registry.disableRegistry();
}
Tomcat tomcat = new Tomcat();
File baseDir = this.baseDirectory != null ? this.baseDirectory : this.createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
Connector connector = new Connector(this.protocol);
connector.setThrowOnFailure(true);
tomcat.getService().addConnector(connector);
this.customizeConnector(connector);
tomcat.setConnector(connector);
tomcat.getHost().setAutoDeploy(false);
this.configureEngine(tomcat.getEngine());
Iterator var5 = this.additionalTomcatConnectors.iterator();
while(var5.hasNext()) {
Connector additionalConnector = (Connector)var5.next();
tomcat.getService().addConnector(additionalConnector);
}
this.prepareContext(tomcat.getHost(), initializers);
return this.getTomcatWebServer(tomcat);
}
最后执行refresh()中调用的finishRefresh()方法
protected void finishRefresh() {
this.clearResourceCaches();
this.initLifecycleProcessor();
this.getLifecycleProcessor().onRefresh();
this.publishEvent((ApplicationEvent)(new ContextRefreshedEvent(this)));
LiveBeansView.registerApplicationContext(this);
}
finishRefresh()方法也在子类中重写了
protected void finishRefresh() {
super.finishRefresh();
WebServer webServer = this.startWebServer();
if (webServer != null) {
this.publishEvent(new ServletWebServerInitializedEvent(webServer, this));
}
}
点进 startWebServer()方法一看
private WebServer startWebServer() {
WebServer webServer = this.webServer;
if (webServer != null) {
webServer.start();
}
return webServer;
}
再点start()方法进去一看,此方法是WebServer接口中的方法,实现类如下
最后在调用TomcatWebServer中的start()方法启动Tomcat。
public void start() throws WebServerException {
synchronized(this.monitor) {
if (!this.started) {
boolean var10 = false;
try {
var10 = true;
this.addPreviouslyRemovedConnectors();
Connector var2 = this.tomcat.getConnector();
if (var2 != null && this.autoStart) {
this.performDeferredLoadOnStartup();
}
this.checkThatConnectorsHaveStarted();
this.started = true;
logger.info("Tomcat started on port(s): " + this.getPortsDescription(true) + " with context path '" + this.getContextPath() + "'");
var10 = false;
} catch (ConnectorStartFailedException var11) {
this.stopSilently();
throw var11;
} catch (Exception var12) {
PortInUseException.throwIfPortBindingException(var12, () -> {
return this.tomcat.getConnector().getPort();
});
throw new WebServerException("Unable to start embedded Tomcat server", var12);
} finally {
if (var10) {
Context context = this.findContext();
ContextBindings.unbindClassLoader(context, context.getNamingToken(), this.getClass().getClassLoader());
}
}
Context context = this.findContext();
ContextBindings.unbindClassLoader(context, context.getNamingToken(), this.getClass().getClassLoader());
}
}
}
文字总结
SpringBoot内置Tomcat启动流程要从main函数入手,而main函数中的run()方法实际上是调用SpringApplication的run()方法。在run()方法中,先创建一个ConfigurableApplicationContext对象,通过createApplicationContext()对象进行创建,这个对象实际上就是JavaWeb的ApplicationContext对象。然后调用refreshContext()方法,在该方法中,又调用了refresh()方法,此方法中定义了Tomcat创建流程,调用ServletWebServerApplicationContext的onRefresh()方法,在该方法中调用了createWebServer()方法,在该方法中,先获取ServletWebServerFactory,再根据工厂获取具体的webServer,此时获取的是TomcatServletWebServerFacotry这个工厂,然后在getWebServer()方法中,创建Tomcat的一些核心组件。然后调用getTomcatWebServer()方法,进行初始化Tomcat。最后调用refresh()中的finishRefresh()方法,该方法被ServletWebServerApplicationContext子类重写,在该方法中调用start()方法将Tomcat启动。