Spring Boot 应用优雅关闭

[复制链接]
发表于 2025-9-20 05:09:29 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
写这篇文章是由于看到 “线程池在使用竣事后应该正确关闭.” 那么假如我们的 Spring 应用都无法正确关闭, 那么线程池肯定也无从保障
1. 优雅关闭


  • kill with pid, without -9
大多数情况下无须在意这个题目, 正确使用 kill 下令关闭就行 (留意不能使用 kill -9)
  1. kill $(cat ./application.pid)
复制代码
默认发送的信号是 SIGTERM,其信号编号为 15. 在无法正常停止历程时使用 -9 (SIGKILL): 欺压立即停止历程。这个信号无法被捕获或忽略,历程会被操纵系统欺压杀死

  • 在启动的时间, 将 pid 和 端标语 记载到文件中 (这些文件在步伐关闭后会自动删除)
  1. public static void main(String[] args) {
  2.     try {
  3.         long start = System.currentTimeMillis();
  4.         SpringApplicationBuilder builder = new SpringApplicationBuilder(App.class);
  5.         builder.beanNameGenerator(FullyQualifiedAnnotationBeanNameGenerator.INSTANCE);
  6.         // startup with pid file and port file
  7.         builder.listeners(new ApplicationPidFileWriter()); // pid file
  8.         // 上面的类可以传入文件路径, 方便使用 kill 命令
  9.         // builder.listeners(new ApplicationPidFileWriter("./bin/shutdown.pid")); // kill $(cat ./bin/shutdown.pid)
  10.         builder.listeners(new WebServerPortFileWriter()); // port file
  11.         builder.run(args);
  12.         long costs = (System.currentTimeMillis() - start) / 1000;
  13.         log.info("### APP STARTED ### It take {} seconds.", costs);
  14.     } catch (Exception e) { log.error(" STARTUP FAILED ", e); }
  15. }
复制代码
2. 增长 URL 接口来关闭步伐


  • 使用 ConfigurableApplicationContext#close
  • 使用 int exit = SpringApplication.exit(context);
  • 关闭应用时实验某段逻辑

    • 注解 @PreDestroy
    • 事件 ContextClosedEvent

  1. @Slf4j
  2. @RestController
  3. public class ShutDownController {
  4.     @Autowired
  5.     private ConfigurableApplicationContext applicationContext;
  6.     @Autowired
  7.     private ApplicationContext context; // 两个 context 都行
  8.     @GetMapping("/shutdown")
  9.     public String shutdown() {
  10.         log.error("1. shutdown begin..."); // 前面的编号是执行顺序
  11.         // 清理资源并关闭
  12.         ((ConfigurableApplicationContext) context).close();
  13.         // applicationContext.close();
  14.         log.error("3. spring application closed.");
  15.         // 在嵌入式应用或在某些情况下,关闭上下文后,JVM 可能不会自动退出。为了确保应用完全终止,使用 System.exit(0)
  16.         System.exit(0); // not necessary
  17.         // log.error("system closed."); // 这里日志日志对象 log 已经失效, 无法打印, 换用 println
  18.         System.err.println("system closed.");
  19.         return "shutdown";
  20.     }
  21.     @GetMapping("/exit")
  22.     public String exit() {
  23.         log.error("1. shutdown begin...");
  24.         // 负责清理资源
  25.         int exit = SpringApplication.exit(context);
  26.         log.error("3. Spring app closed.");
  27.         System.exit(exit); // not necessary
  28.         // log.error("system closed."); // It will fail over
  29.         System.err.println("system closed.");
  30.         return "shutdown";
  31.     }
  32.     @PreDestroy // @PreDestroy 注解: 在 Spring 应用销毁前, 执行某段逻辑
  33.     public void onDestroy() throws Exception {
  34.         log.error("3. Spring Container is destroyed!");
  35.     }
  36.     @Slf4j
  37.     @Component // ContextClosedEvent 事件: 在 Spring 上下文关闭时机, 执行某段逻辑
  38.     public // public 不是必须
  39.     static // @Slf4j is not supported on non-static nested classes.
  40.     class ApplicationShutdown implements ApplicationListener<ContextClosedEvent> {
  41.         @Override
  42.         public void onApplicationEvent(ContextClosedEvent event) {
  43.             log.error("2. graceful shutdown (ContextClosedEvent)");
  44.         }
  45.     }
  46. }
复制代码
Spring 并没有欺压要求内部类必须是 public 或 static。但在最佳实践中:  static 有须要, public 没须要

  • 使用 static 内部类可以克制使用处有不须要的引用,特殊是当内部类不须要访问外部类的实例时。
  • public 不是必须: 假如内部类不须要在外部访问,它可以是 package-private(默认)或 protected。通常发起将其定义为 public 和 static,以提高可维护性。
3. 优雅启动


  • 显示设置参数
  1.     /**
  2.      * 显示配置参数
  3.      */
  4.     public static void printEnvironment(Environment environment) {
  5.         try {
  6.             log.info("******************JVM参数**********************");
  7.             String[] properties =
  8.                     {"java.home", "java.version", "java.vm.name", "java.vm.vendor", "os.name", "user.dir"};
  9.             for (String property : properties) {
  10.                 log.info("* {} = {}", property, System.getProperty(property));
  11.             }
  12.             MemoryMXBean bean = ManagementFactory.getMemoryMXBean();
  13.             MemoryUsage heapUsage = bean.getHeapMemoryUsage();
  14.             log.info("* 初始内存 = " + heapUsage.getInit() / 1024 / 1024 + "M");
  15.             log.info("* 已使用内存 = " + heapUsage.getUsed() / 1024 / 1024 + "M");
  16.             log.info("* 已提交内存 = " + heapUsage.getCommitted() / 1024 / 1024 + "M");
  17.             log.info("* 最大内存 = " + heapUsage.getMax() / 1024 / 1024 + "M");
  18.         } catch (Exception e) {
  19.             log.error("异常: {}", e.getMessage());
  20.         }
  21.     }
复制代码

  • 启动时调用: printEnvironment(builder.context().getEnvironment());
4. 线程池的关闭


  • 由 Spring 管理的线程池, 关闭 Spring 应用上下文时会自动关闭, 包罗 @Async 或 通过 Spring 设置的线程池
  • 自定义线程池 (未通过 Spring 管理), 在关闭应用时, 须要显式地调用 shutdown() 来确保任务可以或许正常完成, 克制丢失. 可以使用上面提到的, 关闭应用时实验某段逻辑的方式实验

    • 注解 @PreDestroy
    • 事件 ContextClosedEvent


(END)
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表