1、前言
对于生产环境的系统而言,管理Spring Boot应用程序的生命周期非常重要。Spring容器借助ApplicationContext处理所有Bean的创建、初始化和销毁。
本文的重点放在整个生命周期的销毁阶段,更具体地说,我们将研究关闭Spring Boot应用程序的不同方法。
2、Shutdown接口
默认情况下除了/shutdown
之外的所有接口在Spring Boot应用程序中都会启用。/shutdown
是Actuator提供的接口。
下面是用于启用该接口的Maven依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
另外,如果我们需要启用Spring Security,我们还需要依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
最后,我们需要在application.properties
文件中启用/shutdown
接口:
management.endpoints.web.exposure.include=*
management.endpoint.shutdown.enabled=true
endpoints.shutdown.enabled=true
注意,我们还要导出我们希望使用的接口,在上面的代码中我们使用通配符*
导出了Actuator提供的全部接口,其中也包括/shutdown
接口。
现在,要关闭Spring Boot应用程序,我们只需调用一个POST方法,如下所示:
curl -X POST http://localhost:port/actuator/shutdown
3、关闭ApplicationContext
我们还可以直接调用ApplicationContext(应用程序上下文)上面的close()方法。
让我们从一个创建应用程序上下文然后关闭它的例子开始:
ConfigurableApplicationContext ctx = new
SpringApplicationBuilder(Application.class).web(WebApplicationType.NONE).run();
System.out.println("Spring Boot application started");
ctx.getBean(TerminateBean.class);
ctx.close();
这将销毁所有的bean、释放锁、然后关闭bean工厂。为了确定应用程序是否关闭,我们使用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();
}
}
这是运行该例子后的输出:
Spring Boot application started
Closing AnnotationConfigApplicationContext@39b43d60
DefaultLifecycleProcessor - Stopping beans in phase 0
Unregistering JMX-exposed beans on shutdown
Spring Container is destroyed!
在这里需要记住一点:在关闭应用程序上下文时,它的父上下文由于具有单独的生命周期,因此不会受到影响。
3.1、关闭当前ApplicationContext
在上面的例子中,我们创建了一个子应用程序上下文,然后使用close()方法销毁了它。
如果我们希望关闭当前的应用程序上下文,一种简单的解决方案是调用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接口并覆写setter方法以获得当前的应用程序上下文。然后在处理请求时会调用close()方法。
然后,我们可以调用新的接口来关闭当前的应用程序上下文:
curl -X POST http://localhost:port/shutdownContext
注意,如果你在真实的程序中添加这样的接口,你需要给它加上一些保护措施。
4、退出SpringApplication
SpringApplication向JVM注册一个Shutdown Hook,以确保应用程序正确退出。
Bean可以通过实现ExitCodeGenerator接口来返回特定的错误代码:
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);
在Java8中可以使用lambda表达式:
SpringApplication.exit(ctx, () -> 0);
调用System.exit(exitCode)后,程序的退出代码是0:
Process finished with exit code 0
5、通过PID结束进程
最后,我们还可以使用bash脚本从程序外面关闭Spring Boot应用程序。此方法的第一步是让Spring Boot应用将其PID写入文件中:
SpringApplicationBuilder app = new SpringApplicationBuilder(Application.class)
.web(WebApplicationType.NONE);
app.build().addListeners(new ApplicationPidFileWriter("./bin/shutdown.pid"));
app.run();
接下来,创建一个shutdown.sh
文件:
kill $(cat ./bin/shutdown.pid)
shutdown.sh
将从shutdown.pid文件中提取进程ID,然后调用kill命令杀掉应用。
6、结论
在本文中,我们介绍了几种可用于关闭正在运行的Spring Boot应用程序的方法。
应该由开发人员从中选择一个合适的方法,这些方法应该根据使用场景与目的挑选出合适的。
例如,当我们需要把程序退出代码传递到另一个环境(例如JVM)以进行进一步的操作时,首选.exit()
。使用PID则更具灵活性,因为我们可以使用bash脚本来启动或重启应用程序。
最后,如果希望通过HTTP从外部关闭应用程序可以使用/shutdown
接口。对于其它的情况可以使用.close()
。