如何在Spring的@PreDestroy前注册SIGTERM优雅关闭同步钩子?
问题描述
我们的Spring应用采用多模块架构,存在以下场景:
- 数据库模块:各模块通过
@Bean(destroyMethod = "shutdown")或@PreDestroy注解定义数据库连接的销毁逻辑(复杂销毁流程使用后者) - 自定义端点模块:包含gRPC、GraphQL、REST等自定义实现的服务器,无法使用Spring默认嵌入式服务器配置
- 需要实现严格的优雅关闭顺序:
- 优先关闭所有端点服务器:拒绝新连接,等待当前请求处理完成
- 仅在端点完全关闭后,再触发所有Bean的
@PreDestroy方法及destroyMethod指定的销毁逻辑
- 限制条件:
server.shutdown=graceful配置不可用,因为使用自定义服务器
解决方案
方法1:使用SmartLifecycle控制关闭顺序
Spring的SmartLifecycle接口可精准管控Bean的启动/关闭顺序,其关闭逻辑会优先于@PreDestroy和destroyMethod执行,是实现该需求的最优方案。
实现步骤
- 创建统一端点服务器接口,规范优雅关闭行为
import java.util.concurrent.TimeUnit; public interface CustomEndpointServer { // 拒绝新连接的方法 void rejectNewConnections(); // 优雅关闭并等待处理完成的方法 void shutdownGracefully(long timeout, TimeUnit unit) throws InterruptedException; }
- 实现各端点服务器的接口适配
以gRPC服务器为例:
import io.grpc.Server; import java.util.concurrent.TimeUnit; public class GrpcEndpointServer implements CustomEndpointServer { private final Server grpcServer; public GrpcEndpointServer(Server grpcServer) { this.grpcServer = grpcServer; } @Override public void rejectNewConnections() { grpcServer.shutdown(); // gRPC原生方法,拒绝新连接 } @Override public void shutdownGracefully(long timeout, TimeUnit unit) throws InterruptedException { if (!grpcServer.awaitTermination(timeout, unit)) { grpcServer.shutdownNow(); // 超时后强制关闭 } } }
- 创建
SmartLifecycle实现类,统一管理端点关闭
import org.springframework.context.SmartLifecycle; import org.springframework.stereotype.Component; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @Component public class EndpointGracefulShutdownManager implements SmartLifecycle { private final List<CustomEndpointServer> endpointServers; private volatile boolean isRunning = false; // 注入所有自定义端点服务器实例 public EndpointGracefulShutdownManager(List<CustomEndpointServer> endpointServers) { this.endpointServers = endpointServers; } @Override public void start() { isRunning = true; // 若需统一启动端点,可在此添加逻辑 } @Override public void stop() { // 第一步:拒绝所有新连接 endpointServers.forEach(CustomEndpointServer::rejectNewConnections); // 第二步:等待所有端点完成当前请求并关闭 CountDownLatch shutdownLatch = new CountDownLatch(endpointServers.size()); endpointServers.forEach(server -> { try { server.shutdownGracefully(30, TimeUnit.SECONDS); // 设置30秒超时 shutdownLatch.countDown(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }); try { shutdownLatch.await(); // 阻塞直到所有端点关闭完成 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } isRunning = false; } @Override public boolean isRunning() { return isRunning; } // 设置更高的phase值,确保此Bean的关闭逻辑优先执行 // phase值越大,关闭顺序越靠前(默认Bean销毁的phase为0) @Override public int getPhase() { return 100; } }
方法2:监听ContextClosedEvent并设置最高优先级
通过监听Spring容器的ContextClosedEvent,并将监听器优先级设为最高,确保端点关闭逻辑最先执行。
实现示例
import org.springframework.context.ApplicationListener; import org.springframework.context.event.ContextClosedEvent; import org.springframework.core.Ordered; import org.springframework.stereotype.Component; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @Component public class EndpointShutdownListener implements ApplicationListener<ContextClosedEvent>, Ordered { private final List<CustomEndpointServer> endpointServers; public EndpointShutdownListener(List<CustomEndpointServer> endpointServers) { this.endpointServers = endpointServers; } @Override public void onApplicationEvent(ContextClosedEvent event) { // 执行与SmartLifecycle中一致的端点关闭逻辑 endpointServers.forEach(CustomEndpointServer::rejectNewConnections); CountDownLatch shutdownLatch = new CountDownLatch(endpointServers.size()); endpointServers.forEach(server -> { try { server.shutdownGracefully(30, TimeUnit.SECONDS); shutdownLatch.countDown(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }); try { shutdownLatch.await(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } // 设置最高优先级,确保此监听器最先处理关闭事件 @Override public int getOrder() { return Ordered.HIGHEST_PRECEDENCE; } }
关键说明
SmartLifecycle是Spring生命周期管理的原生组件,与容器关闭流程集成更紧密,推荐优先使用- 若选择事件监听方式,必须确保监听器优先级为
Ordered.HIGHEST_PRECEDENCE,避免被其他监听器抢先执行 - 所有端点的优雅关闭逻辑需严格遵循「拒绝新连接→等待当前请求完成→释放资源」的顺序
内容的提问来源于stack exchange,提问作者Oleksandr
相关产品推荐
相关产品推荐

