@Override publicvoidrun(){ Set contexts; Set closedContexts; Set actions; synchronized (SpringApplicationShutdownHook.class) { this.inProgress = true; contexts = new LinkedHashSet<>(this.contexts); closedContexts = new LinkedHashSet<>(this.closedContexts); actions = new LinkedHashSet<>(this.handlers.getActions()); } contexts.forEach(this::closeAndWait); closedContexts.forEach(this::closeAndWait); actions.forEach(Runnable::run); }
// Call ConfigurableApplicationContext.close() and wait until the context becomes inactive. // We can't assume that just because the close method returns that the context is actually inactive. // It could be that another thread is still in the process of disposing beans. // 关闭 ConfigurableApplicationContext,等待 context 变为 inactive,超时时间默认 10S privatevoidcloseAndWait(ConfigurableApplicationContext context){ if (!context.isActive()) { return; } context.close(); try { int waited = 0; while (context.isActive()) { if (waited > TIMEOUT) { thrownew TimeoutException(); } Thread.sleep(SLEEP); waited += SLEEP; } } catch (InterruptedException ex) { Thread.currentThread().interrupt(); logger.warn("Interrupted waiting for application context " + context + " to become inactive"); } catch (TimeoutException ex) { logger.warn("Timed out waiting for application context " + context + " to become inactive", ex); } }
ConfigurableApplicationContext#close()
方法注意事项:
Close this application context, releasing all resources and locks that the implementation might hold. This includes destroying all cached singleton beans.
Note: Does not invoke close on a parent context; parent contexts have their own, independent lifecycle.
This method can be called multiple times without side effects: Subsequent close calls on an already closed context will be ignored.
privatefinal Set contexts = new LinkedHashSet<>();
private
final Set closedContexts = Collections.newSetFromMap(new WeakHashMap<>());
privatefinal ApplicationContextClosedListener contextCloseListener = new ApplicationContextClosedListener();
// ApplicationListener to track closed contexts. privateclassApplicationContextClosedListenerimplementsApplicationListener<ContextClosedEvent> {
@Override publicvoidonApplicationEvent(ContextClosedEvent event){ // The ContextClosedEvent is fired at the start of a call to {@code close()} // and if that happens in a different thread then the context may still be // active. Rather than just removing the context, we add it to a {@code // closedContexts} set. This is weak set so that the context can be GC'd once // the {@code close()} method returns. synchronized (SpringApplicationShutdownHook.class) { ApplicationContext applicationContext = event.getApplicationContext(); SpringApplicationShutdownHook.this.contexts.remove(applicationContext); SpringApplicationShutdownHook.this.closedContexts .add((ConfigurableApplicationContext) applicationContext); } }
}
基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
[2023-09-22 09:11:08.533] INFO [Thread-5] org.apache.coyote.http11.Http11NioProtocol 173 [] [TID: N/A] - Pausing ProtocolHandler ["http-nio-8080"] [2023-09-22 09:11:10.842] INFO [Thread-5] org.apache.coyote.http11.Http11NioProtocol 173 [] [TID: N/A] - Stopping ProtocolHandler ["http-nio-8080"] [2023-09-22 09:11:10.863] ERROR [http-nio-8080-exec-3] [bcb01a34-9721-461f-ad3d-2f71c386ff10] [TID: N/A] - controller system exception, java.nio.channels.ClosedChannelException org.apache.catalina.connector.ClientAbortException: java.nio.channels.ClosedChannelException at org.apache.catalina.connector.OutputBuffer.realWriteBytes(OutputBuffer.java:353) at org.apache.catalina.connector.OutputBuffer.flushByteBuffer(OutputBuffer.java:784) at org.apache.catalina.connector.OutputBuffer.doFlush(OutputBuffer.java:299) [2023-09-22 09:11:10.915] INFO [Thread-5] org.apache.coyote.http11.Http11NioProtocol 173 [] [TID: N/A] - Destroying ProtocolHandler ["http-nio-8080"]
server.shutdown=GRACEFUL
配置:发送 HTTP 请求后,停止 SpringBoot 应用,控台输出:
Commencing graceful shutdown. Waiting for active requests to complete
,SpringBoot 进程会等待
active requests
完成,再退出。
[2023-09-22 09:18:12.507] INFO [Thread-5] org.springframework.boot.web.embedded.tomcat.GracefulShutdown 53 [] [TID: N/A] - Commencing graceful shutdown. Waiting for active requests to complete [2023-09-22 09:18:12.507] INFO [tomcat-shutdown] org.apache.coyote.http11.Http11NioProtocol 173 [] [TID: N/A] - Pausing ProtocolHandler ["http-nio-8080"] [2023-09-22 09:18:17.633] INFO [tomcat-shutdown] org.springframework.boot.web.embedded.tomcat.GracefulShutdown 78 [] [TID: N/A] - Graceful shutdown complete [2023-09-22 09:18:17.637] INFO [Thread-5] org.apache.coyote.http11.Http11NioProtocol 173 [] [TID: N/A] - Pausing ProtocolHandler ["http-nio-8080"] [2023-09-22 09:18:17.645] INFO [Thread-5] org.apache.coyote.http11.Http11NioProtocol 173 [] [TID: N/A] - Stopping ProtocolHandler ["http-nio-8080"] [2023-09-22 09:18:17.657] INFO [Thread-5] org.apache.coyote.http11.Http11NioProtocol 173 [] [TID: N/A] - Destroying ProtocolHandler ["http-nio-8080"]
voidshutDownGracefully(GracefulShutdownCallback callback){ logger.info("Commencing graceful shutdown. Waiting for active requests to complete"); new Thread(() -> doShutdown(callback), "tomcat-shutdown").start(); }
privatevoiddoShutdown(GracefulShutdownCallback callback){ List connectors = getConnectors(); connectors.forEach(this::close); try { for (Container host : this.tomcat.getEngine().findChildren()) { for (Container context : host.findChildren()) { while (isActive(context)) { if (this.aborted) { logger.info("Graceful shutdown aborted with one or more requests still active"); callback.shutdownComplete(GracefulShutdownResult.REQUESTS_ACTIVE); return; } Thread.sleep(50); } } }
// Close the server socket (to prevent further connections) if the server socket was originally bound on start() (rather than on init()). publicfinalvoidcloseServerSocketGraceful(){ if (bindState == BindState.BOUND_ON_START) { // Stop accepting new connections acceptor.stop(-1); // Release locks that may be preventing the acceptor from stopping releaseConnectionLatch(); unlockAccept(); // Signal to any multiplexed protocols (HTTP/2) that they may wish // to stop accepting new streams getHandler().pause(); // Update the bindState. This has the side-effect of disabling // keep-alive for any in-progress connections bindState = BindState.SOCKET_CLOSED_ON_STOP; try { doCloseServerSocket(); } catch (IOException ioe) { getLog().warn(sm.getString("endpoint.serverSocket.closeFailed", getName()), ioe); } } }