关闭一个Spring boot的应用的几种方式

1,862 阅读2分钟

如何关闭一个Spring boot的应用?

方法对比

 actuator关闭上下文退出应用PID杀死应用进程
优点HTTP远程关闭自定义清理工作传递退出时INT值,方便JVM后续操作灵活,甚至可以启动和重启应用

actuator

在pom文件中引入依赖以使用actuator.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

默认情况下,shutdown端点都是默认不开启的.可以通过在application.properties中添加以下代码:

management.endpoints.web.exposure.include=*
management.endpoint.shutdown.enabled=true
endpoints.shutdown.enabled=true

接下来通过http访问的方式,实现关闭spring boot 应用.

curl -X POST localhost:port/actuator/shutdown

关闭上下文

通过调用上下文的close()方法关闭上下文.

举个例子:

ConfigurableApplicationContext ctx = new 
  SpringApplicationBuilder(Application.class).web(WebApplicationType.NONE).run();
System.out.println("Spring Boot application started");
ctx.getBean(TerminateBean.class);
ctx.close();

close()方法会销毁所有的bean并释放锁,最后关闭所有的bean factory.

为了验证以上代码起到了想要的效果,使用spring生命周期容器被销毁前会调用的注解@PreDestroy来测试.

public class TerminateBean {

    @PreDestroy
    public void onDestroy() throws Exception {
        System.out.println("Spring Container is destroyed!");
    }
}

声明以下需要用到的bean

@Configuration
public class ShutdownConfig {

    @Bean
    public TerminateBean getTerminateBean() {
        return new TerminateBean();
    }
}

运行应用会得到如下日志:

17:01:20.542 [main] INFO  com.baeldung.shutdown.Application - Started Application in 5.01 seconds (JVM running for 5.999)
Spring Boot application started
Spring Container is destroyed!
17:01:20.549 [main] INFO  o.s.o.j.LocalContainerEntityManagerFactoryBean - Closing JPA EntityManagerFactory for persistence unit 'default'
17:01:20.549 [main] INFO  o.h.t.s.i.SchemaDropperImpl$DelayedDropActionImpl - HHH000477: Starting delayed evictData of schema as part of SessionFactory shut-down'
17:01:20.552 [main] INFO  com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown initiated...
17:01:20.556 [main] INFO  com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Shutdown completed.

Process finished with exit code 0

ps:关闭上下文时,因为spring隔离的生命周期并不会影响父级上下文.

以上方法是创建了一个新的上下文并调用close()方法.如果想要关闭现在使用的context,最简单的方式就是使用上文的actuator的shutdown端点.当然也可以通过使用自定义的端点实现此功能.

@RestController
public class ShutdownController implements ApplicationContextAware {

    private ApplicationContext context;

    @PostMapping("/shutdownContext")
    public void shutdownContext() {
        ((ConfigurableApplicationContext) context.close();
    }

    @Override
    public void setApplicationContext(ApplicationContext ctx) throws BeansException {
        this.context = ctx;
    }
}

实现ApplicationContextAware接口并重写setApplicationContext方法获得当前应用的context.

同样,通过http访问的方式,实现关闭spring boot应用.

curl -X POST localhost:port/shutdownContext

ps:对于自定义的endpoint,做好安全防护.

退出应用

spring 应用会在JVM中注册一个shutdown的钩子函数来确保退出恰当地退出应用.

ConfigurableApplicationContext ctx = new SpringApplicationBuilder(Application.class)
  .web(WebApplicationType.NONE).run();

int exitCode = SpringApplication.exit(ctx, new ExitCodeGenerator() {
@Override
public int getExitCode() {
        // return the error code
        return 0;
    }	
});

System.exit(exitCode);

调用system.exit(),会关闭JVM并返回INT值.

PID杀死应用进程

使用bash脚本在应用外杀死应用.

SpringApplicationBuilder app = new SpringApplicationBuilder(Application.class)
  .web(WebApplicationType.NONE);
app.build().addListeners(new ApplicationPidFileWriter("./bin/shutdown.pid"));
app.run();

接着,创建一个shutdown.bat脚本:

kill -2 $(cat ./bin/shutdown.pid)

执行该脚本即可杀死应用.

gracefully shutdown

在Spring Boot 2.3 之后 ,支持全部四种web server的优雅关机,只需要在配置文件application.yml中加入

server:
  shutdown: graceful

优雅关机的情况下,web server会停止接收新的请求.同时对于在优雅关机阶段进来的部分请求,server会等待一个时间段来完成这些请求.这个时间段同样可以通过配置设置,如下:

spring:
  lifecycle:
    timeout-per-shutdown-phase: 30s
    

参考文章原文