You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Spring的@PreDestroy前注册SIGTERM优雅关闭同步钩子?

问题描述

我们的Spring应用采用多模块架构,存在以下场景:

  • 数据库模块:各模块通过@Bean(destroyMethod = "shutdown")或@PreDestroy注解定义数据库连接的销毁逻辑(复杂销毁流程使用后者)
  • 自定义端点模块:包含gRPC、GraphQL、REST等自定义实现的服务器,无法使用Spring默认嵌入式服务器配置
  • 需要实现严格的优雅关闭顺序:
    1. 优先关闭所有端点服务器:拒绝新连接,等待当前请求处理完成
    2. 仅在端点完全关闭后,再触发所有Bean的@PreDestroy方法及destroyMethod指定的销毁逻辑
  • 限制条件:server.shutdown=graceful配置不可用,因为使用自定义服务器
解决方案

方法1:使用SmartLifecycle控制关闭顺序

Spring的SmartLifecycle接口可精准管控Bean的启动/关闭顺序,其关闭逻辑会优先于@PreDestroy和destroyMethod执行,是实现该需求的最优方案。

实现步骤

  1. 创建统一端点服务器接口,规范优雅关闭行为
import java.util.concurrent.TimeUnit;

public interface CustomEndpointServer {
    // 拒绝新连接的方法
    void rejectNewConnections();
    // 优雅关闭并等待处理完成的方法
    void shutdownGracefully(long timeout, TimeUnit unit) throws InterruptedException;
}
  1. 实现各端点服务器的接口适配
    以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(); // 超时后强制关闭
        }
    }
}
  1. 创建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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 18:48:18