SpringBoot是如何保证服务启动后不自动停止的
一般项目执行后,当程序结束会自动关闭程序。但Springboot项目,启动后,只要不发生error错误,一般不会自动停止。
这是为什么呢?
简单Java阻止停止
为了保证一个服务能够持续有效地对外提供服务,一般会有相应的处理方式,比如:
- 服务器上的守护进程脚本
但是,在Java代码层面,除了shell脚本之外,还有一种很特别的方式,保证服务不会执行后停止。
- 死循环!文雅点叫自旋锁。
比如下面的例子:
public class ThreadDemo2 {
public static volatile boolean isDead = false;
public static void main(String[] args) throws InterruptedException {
while (!isDead){
Thread.sleep(4000);
}
}
}
程序执行后,会进入自旋锁的代码逻辑中,每隔4s,检查一下isDead值。
如果isDead一直为false,那么程序将不会自动停止。
但是,设置在主线程中,此处自旋锁触发后,导致后续代码并不会继续执行,影响到后面的逻辑处理,显然是不可取的。
如果,单独开辟一个新的线程,去处理这个活,主线程依旧去执行别的逻辑呢?
public class TestThread {
public static volatile boolean isDead = false;
public static void main(String[] args) throws InterruptedException {
// Thread thread = new Thread(new Runnable() {
// @Override
// public void run() {
// System.out.println("---------");
// try {
// while (!isDead){
// Thread.sleep(10000L);
// }
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// }
// });
Thread thread = new Thread("container-1") {
@Override
public void run() {
System.out.println("---------");
try {
while (!isDead) {
Thread.sleep(4000L);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
thread.setDaemon(false);
thread.start();
// 主线程暂停40s
Thread.sleep(40000L);
// 变更状态
isDead = true;
}
}
单独设定一个非守护进程的线程,去干这个活,主线程依旧可以继续执行其他的事。
基于这个思想,接下来看看源码中Springboot底层是如何实现的。
Springboot 底层实现
以Springboot默认整合Tomcat为例。
查看SpringApplication.run
Springboot的项目执行,依据的是run方法,其中的实现方式如下:
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class<?>[] { primarySource }, args);
}
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return new SpringApplication(primarySources).run(args);
}
继续查看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);
// 暂时不看别的逻辑,就看 refreshContext
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;
}
其他逻辑暂时不看,就看其中的refreshContext(context);
refreshContext(context)
private void refreshContext(ConfigurableApplicationContext context) {
refresh(context);
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
}
protected void refresh(ApplicationContext applicationContext) {
Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
((AbstractApplicationContext) applicationContext).refresh();
}
org.springframework.context.support.AbstractApplicationContext#refresh
在其中,定义了synchronized保证了启动加载时的线程安全性问题:
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
其中定义程序是否保活的逻辑,在onRefresh()中。
org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#onRefresh
@Override
protected void onRefresh() {
super.onRefresh();
try {
createWebServer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start web server", ex);
}
}
createWebServer()主要是构建服务容器,这里以tomcat为例。点击进去查看其实现:
private void createWebServer() {
WebServer webServer = this.webServer;
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) {
ServletWebServerFactory factory = getWebServerFactory();
this.webServer = factory.getWebServer(getSelfInitializer());
}
else if (servletContext != null) {
try {
getSelfInitializer().onStartup(servletContext);
}
catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context", ex);
}
}
initPropertySources();
}
在服务容器创建时,就会定义其保活线程,查看getWebServer处理逻辑:
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
if (this.disableMBeanRegistry) {
Registry.disableRegistry();
}
Tomcat tomcat = new Tomcat();
File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
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);
return getTomcatWebServer(tomcat);
}
查看tomcatweb服务生成方式逻辑:
public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
Assert.notNull(tomcat, "Tomcat Server must not be null");
this.tomcat = tomcat;
this.autoStart = autoStart;
initialize();
}
查看init逻辑:
private void initialize() throws WebServerException {
logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
synchronized (this.monitor) {
try {
addInstanceIdToEngineName();
Context context = findContext();
context.addLifecycleListener((event) -> {
if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) {
// Remove service connectors so that protocol binding doesn't
// happen when the service is started.
removeServiceConnectors();
}
});
// Start the server to trigger initialization listeners
this.tomcat.start();
// We can re-throw failure exception directly in the main thread
rethrowDeferredStartupExceptions();
try {
ContextBindings.bindClassLoader(context, context.getNamingToken(), getClass().getClassLoader());
}
catch (NamingException ex) {
// Naming is not enabled. Continue
}
// Unlike Jetty, all Tomcat threads are daemon threads. We create a
// blocking non-daemon to stop immediate shutdown
startDaemonAwaitThread();
}
catch (Exception ex) {
stopSilently();
destroySilently();
throw new WebServerException("Unable to start embedded Tomcat", ex);
}
}
}
可以看到,在tomcat的服务容器构建完成后,其中调用了一个startDaemonAwaitThread();开启了一个守护线程?
查看其中的逻辑:
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();
}
开启了一个新的非守护类型的线程,去执行了await()。
查看await()的实现逻辑:
红框中的代码逻辑,是否似曾相识!
整体思维逻辑
SpringApplication.run
-->
refreshContext
-- >
org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#refresh
-- >
org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext#createWebServer
-- >
org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory#getWebServer
->
org.springframework.boot.web.embedded.tomcat.TomcatWebServer#TomcatWebServer(org.apache.catalina.startup.Tomcat, boolean)
-->
org.springframework.boot.web.embedded.tomcat.TomcatWebServer#initialize
-->
org.springframework.boot.web.embedded.tomcat.TomcatWebServer#startDaemonAwaitThread
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
2021-11-09 压力测试工具jmeter