一. 问题提出
一个标准的Springboot工程main方法执行完毕后,不会退出应用,会等待客户端请求。 但是一个普通的Java工程在main方法执行完毕后,会自动退出应用。
那么Spingboot是怎么做到这个能力的呢?
二. 答案
先说答案
我们知道Java程序退出有几种方法,一种是程序主动退出,还有一种是非守护线程全部退出。
那么Springboot不退出的方法就是第二种,非守护线程保持不退出,从而使程序不退出。
Springboot在启动的时候,具体来说是执行run方法的时候,会调用tomcat(假如以tomcat作为内嵌容器)的启动方法,然后拉起一个非守护线程,具体分析如下。
#三. 代码分析
3.1 程序入口代码
程序入口代码我们肯定非常熟悉,就是调用SpringApplication.run方法
public class RuoYiApplication
{
public static void main(String[] args)
{
SpringApplication.run(RuoYiApplication.class, args);
}
}
3.2 refreshContext方法
我们省略run方法里面其他代码,专门程序不退出这块功能实现的代码,如下
public ConfigurableApplicationContext run(String... args) {
...
try {
...
refreshContext(context);
...
}
...
return context;
}
在run方法里面,我们主要看refreshContext方法,这里面实现了程序不退出的逻辑。
protected void refresh(ConfigurableApplicationContext applicationContext) {
applicationContext.refresh();
}
调用的是applicationContext.refresh();
refresh方法是个接口,具体实现类是ServletWebServerApplicationContext(因为一般是Servlet类型,Springboot是根据程序推断出来的)
然后ServletWebServerApplicationContext的refresh是调用父类的refresh方法,也就是
AbstractApplicationContext
如下:
@Override
public final void refresh() throws BeansException, IllegalStateException {
try {
super.refresh();
}
catch (RuntimeException ex) {
WebServer webServer = this.webServer;
if (webServer != null) {
webServer.stop();
}
throw ex;
}
}
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
...
try {
...
// Initialize other special beans in specific context subclasses.
onRefresh();
...
}
}
我们继续省略其他代码,直接关注onRefresh()方法,有意思的是onRefresh()方法是个空实现,所以逻辑要看他的子类,我们当然还是看Servlet的实现。如下,servletWebServerApplicationContext.onRefresh()
@Override
onRefresh() {
super.onRefresh();
{
createWebServer();
}
(Throwable ex) {
ApplicationContextException(, ex);
}
}
调用的也是父类,那就不看去了,主要看createWebServer()方法,顾名思义,就是创建web容器。
3.3 创建Web容器createWebServer
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) {
...
this.webServer = factory.getWebServer(getSelfInitializer());
...
}
else if (servletContext != null) {
...
}
initPropertySources();
}
主要看createWebServer里面的factory.getWebServer()
这个也是接口,看他的实现,我们以Tomcat为例,如下。
3.4 Tomcat的getWebServer
先说一下Springboot能继承Tomcat的原因,是因为Tomcat提供了一个Jar包,允许第三方以Jar包的方式集成,Springboot初始化Tomcat代码如下,重点看最后一行getTomcatWebServer()
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
...
//初始化Tomcat的对象,设置属性
Tomcat tomcat = new Tomcat();
File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
for (LifecycleListener listener : this.serverLifecycleListeners) {
tomcat.getServer().addLifecycleListener(listener);
}
Connector connector = new Connector(this.protocol);
connector.setThrowOnFailure(true);
tomcat.getService().addConnector(connector);
customizeConnector(connector);
tomcat.setConnector(connector);
tomcat.getHost().setAutoDeploy(false);
configureEngine(tomcat.getEngine());
for (Connector additionalConnector : this.additionalTomcatConnectors) {
tomcat.getService().addConnector(additionalConnector);
}
prepareContext(tomcat.getHost(), initializers);
//管理Tomcat,需要重点看。
return getTomcatWebServer(tomcat);
}
我们重点看getTomcatWebServer的实现
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
return new TomcatWebServer(tomcat, getPort() >= 0, getShutdown());
}
里面初始化了一个TomcatWebServer对象,TomcatWebServer对象是Tomcat的管理类,Tomcat类是Tomcat容器提供的第三方类,TomcatWebServer是Spring提供的,用来管理Tomcat.我们继续看TomcatWebServer的实现。
3.5 TomcatWebServer的初始化
public TomcatWebServer(Tomcat tomcat, boolean autoStart, Shutdown shutdown) {
Assert.notNull(tomcat, "Tomcat Server must not be null");
this.tomcat = tomcat;
this.autoStart = autoStart;
this.gracefulShutdown = (shutdown == Shutdown.GRACEFUL) ? new GracefulShutdown(tomcat) : null;
initialize();
}
在TomcatWebServer的初始化方法里面,主要是保存了tomcat对象(因为要启动,停止tomcat),然后调用initialize()方法。
private void initialize() throws WebServerException {
logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
synchronized (this.monitor) {
try {
...
// Start the server to trigger initialization listeners
this.tomcat.start();
...
// Unlike Jetty, all Tomcat threads are daemon threads. We create a
// blocking non-daemon to stop immediate shutdown
startDaemonAwaitThread();
}
catch (Exception ex) {
...
}
}
}
我们去掉了不相关代码,我们只看相关的。
首先看这行日志,这行日志大家熟悉吧??
logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
然后启动了tomcat
this.tomcat.start();
然后重点看这行
// Unlike Jetty, all Tomcat threads are daemon threads. We create a
// blocking non-daemon to stop immediate shutdown
startDaemonAwaitThread();
这里它详细解释了这么做的原因,因为tomat不像Jetty,Tomcat的所有线程都是守护线程。所以为了保证程序启动完毕后不立即退出,特地启动一个非守护线程(non-daemon)
3.6 启动非守护线程
private void startDaemonAwaitThread() {
Thread awaitThread = new Thread("container-" + (containerCounter.get())) {
@Override
public void run() {
TomcatWebServer.this.tomcat.getServer().await();
}
};
awaitThread.setContextClassLoader(getClass().getClassLoader());
awaitThread.setDaemon(false);
awaitThread.start();
}
启动了一个非守护线程awaitThread,把守护线程属性设置成false,而且这个线程的run方法需要等待tomcat结束才会结束。
这样Springboot运行完毕后,程序不立即退出以及Springboot和Tomcat集成的原理就明白了。