Spring Boot应用关闭分析

优质博文:IT-BLOG-CN

一、使用spring容器的close方法关闭。

可通过在代码中获取SpringContext并调用close方法去关闭容器。

使用SpringApplication的exit方法。

public static int exit(ApplicationContext context,
            ExitCodeGenerator... exitCodeGenerators) {
   
    
    
        Assert.notNull(context, "Context must not be null");
        int exitCode = 0;
        try {
   
    
    
            try {
   
    
    
                //获取ExitCodeGenerator的Bean并用ExitCodeGenerators管理
                ExitCodeGenerators generators = new ExitCodeGenerators();
                Collection<ExitCodeGenerator> beans = context
                        .getBeansOfType(ExitCodeGenerator.class).values();
                generators.addAll(exitCodeGenerators);
                generators.addAll(beans);
                exitCode = generators.getExitCode();
                if (exitCode != 0) {
   
    
    
                    // 发布ExitCodeEvent事件
                    context.publishEvent(new ExitCodeEvent(context, exitCode));
                }
            }
            finally {
   
    
    
                close(context);
            }
        }
        catch (Exception ex) {
   
    
    
            ex.printStackTrace();
            exitCode = (exitCode != 0) ? exitCode : 1;
        }
        return exitCode;
    }

上述代码,总的来说就是,获取ExitCodeGenerator的Bean并用ExitCodeGenerators管理, 注意getExitCode()的实现是取ExitCodeGenerator集合中最大的exitCode作为最终exitCode,

最后,关闭容器。

二、exitCode

SpringApplication#exit方法返回的exitCode还需要自行调用System#exit方法去指定。 该System#exit(int code)的参数,能被父进程获取并使用。一般按照惯例0为程序正常退出,非0位不正常退出。 我写了的运行demo:

@Slf4j
@SpringBootApplication
public class ApplicationMainShutDownBySpringApplication {
   
    
    

    public static void main(String[] args) {
   
    
    

        ConfigurableApplicationContext ctx = new SpringApplicationBuilder(ApplicationMainShutDownBySpringApplication.class).build().run(args);

        int exitCode = SpringApplication.exit(ctx);
        log.info("exitCode is {}!", exitCode);
        System.exit(exitCode);
    }

    @Bean
    public ExitCodeGenerator exitCodeGenerator() {
   
    
    
        return () -> 10;
    }
}
执行window bat:
java -jar spring-boot-mvc-shutdown-demo.jar & echo %ERRORLEVEL%

省略其他日志最终输出:
10

可以看最终输出是:10。

二、使用Actuator的shutdown http接口或JMX

可参考Actuator包的ShutdownEndpoint,实质上是调用spring容器的close方法关闭的。

http方式关闭:
在这里插入图片描述

JMX方式关闭:
在这里插入图片描述

三、kill进程

一般的kill(kill -15)会触发应用在refreshContext时(并且SpringApplication实例的registerShutdownHook为true时)加上的注册到JVM的shutdownhook。

public void registerShutdownHook() {
   
    
    
    if (this.shutdownHook == null) {
   
    
    
        // No shutdown hook registered yet.
        this.shutdownHook = new Thread() {
   
    
    
            @Override
            public void run() {
   
    
    
                synchronized (startupShutdownMonitor) {
   
    
    
                    doClose();
                }
            }
        };
        Runtime.getRuntime().addShutdownHook(this.shutdownHook);
    }
}

四、pidFile生成

spring boot提供ApplicationPidFileWriter类,将运行时pid写入指定文件中。
应用场景:可供kill使用。kill $(cat application.pid)

添加步骤:

【1】增加监听器

org.springframework.context.ApplicationListener=\
org.springframework.boot.context.ApplicationPidFileWriter

【2】配置项

spring:
pid:
 fail-on-write-error: true
 #可通过此文件里的pid去关闭应用
 file: ./application.pid

五、spring容器close代码分析

这里对容器关闭进行一些分析,以注释的形式写在下面。

    /**
     * Close this application context, destroying all beans in its bean factory.
     * <p>Delegates to {@code doClose()} for the actual closing procedure.
     * Also removes a JVM shutdown hook, if registered, as it's not needed anymore.
     * @see #doClose()
     * @see #registerShutdownHook()
     */
    @Override
    public void close() {
   
    
    
        synchronized (this.startupShutdownMonitor) {
   
    
    
            //委托给钩子方法doClose去做
            doClose();
            // If we registered a JVM shutdown hook, we don't need it anymore now:
            // We've already explicitly closed the context.
            if (this.shutdownHook != null) {
   
    
    
                try {
   
    
    
                    //去掉shutdown hook
                    Runtime.getRuntime().removeShutdownHook(this.shutdownHook);
                }
                catch (IllegalStateException ex) {
   
    
    
                    // ignore - VM is already shutting down
                }
            }
        }
    }
    protected</