Linux 如何以正确的方式关闭Spring启动应用程序?

Linux 如何以正确的方式关闭Spring启动应用程序?,linux,spring,spring-boot,Linux,Spring,Spring Boot,在Spring引导文档中,他们说“每个SpringApplication都将向JVM注册一个关机钩子,以确保ApplicationContext在退出时正常关闭。” 当我在shell命令上单击ctrl+c时,应用程序可以正常关闭。如果在生产机器中运行应用程序,则必须使用以下命令 java-jarproapplicaton.jar。但是我不能关闭shell终端,否则它将关闭进程 如果我运行像nohupjava-jarproapplicaton.jar&这样的命令,我就不能使用ctrl+c优雅地关闭

在Spring引导文档中,他们说“每个SpringApplication都将向JVM注册一个关机钩子,以确保ApplicationContext在退出时正常关闭。”

当我在shell命令上单击
ctrl+c
时,应用程序可以正常关闭。如果在生产机器中运行应用程序,则必须使用以下命令
java-jarproapplicaton.jar
。但是我不能关闭shell终端,否则它将关闭进程

如果我运行像
nohupjava-jarproapplicaton.jar&
这样的命令,我就不能使用
ctrl+c
优雅地关闭它

public class SomeClass {
    @Autowired
    private ApplicationContext context

    public void close() {
        SpringApplication.exit(context);
    }
}

在生产环境中启动和停止Spring Boot应用程序的正确方法是什么?

如果您使用的是执行器模块,则可以通过
JMX
HTTP
关闭应用程序(如果启用了端点)

添加到
应用程序属性中

endpoints.shutdown.enabled=true

将提供以下URL:

/exactor/shutdown
-允许应用程序正常关闭(默认情况下未启用)

根据端点的公开方式,敏感参数可以用作安全提示

例如,当通过
HTTP
访问敏感端点时,它们需要用户名/密码(如果未启用web安全性,则只需禁用)


中,如果您正在使用maven,则可以使用

(其中嵌入)将输出一个带有start/stop参数的shell脚本。
stop
将正常关闭/终止您的Spring应用程序

    new SpringApplicationBuilder(Application.class)
            .listeners(new ApplicationErrorListener())
            .run(args);  
@SpringBootConfiguration
public class ExampleMain {
    @Bean
    MyBean myBean() {
        return new MyBean();
    }

    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(ExampleMain.class, args);
        MyBean myBean = context.getBean(MyBean.class);
        myBean.doSomething();

        //no need to call context.registerShutdownHook();
    }

    private static class MyBean {

        @PostConstruct
        public void init() {
            System.out.println("init");
        }

        public void doSomething() {
            System.out.println("in doSomething()");
        }

        @PreDestroy
        public void destroy() {
            System.out.println("destroy");
        }
    }
}

相同的脚本可用于将maven应用程序用作linux服务。

您可以使springboot应用程序将PID写入文件,并且可以使用PID文件停止或重新启动,或者使用bash脚本获取状态。要将PID写入文件,请使用ApplicationPidFileWriter将侦听器注册到SpringApplication,如下所示:

SpringApplication application = new SpringApplication(Application.class);
application.addListeners(new ApplicationPidFileWriter("./bin/app.pid"));
application.run();
然后编写一个bash脚本来运行spring引导应用程序


现在您可以使用脚本启动、停止或重新启动。

至于@Jean-Philippe Bond的答案

下面是一个maven快速示例,maven用户可以使用spring boot starter actuator将HTTP端点配置为关闭spring boot web应用程序,以便您可以复制和粘贴:

1.Maven pom.xml:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
列出了所有端点:

3.发送post方法以关闭应用程序:

curl -X POST localhost:port/shutdown

安全说明: 如果需要关闭方法auth protected,可能还需要

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

org.springframework.boot

这里是另一个不需要您更改代码或公开关闭端点的选项。创建以下脚本并使用它们启动和停止应用程序

start.sh

#!/bin/bash
java -jar myapp.jar & echo $! > ./pid.file &
#!/bin/bash
kill $(cat ./pid.file)
#!/bin/bash
nohup ./start.sh > foo.out 2> foo.err < /dev/null &
启动应用程序并将进程id保存在文件中

停止。sh

#!/bin/bash
java -jar myapp.jar & echo $! > ./pid.file &
#!/bin/bash
kill $(cat ./pid.file)
#!/bin/bash
nohup ./start.sh > foo.out 2> foo.err < /dev/null &
使用保存的进程id停止应用程序

启动\u静音。sh

#!/bin/bash
java -jar myapp.jar & echo $! > ./pid.file &
#!/bin/bash
kill $(cat ./pid.file)
#!/bin/bash
nohup ./start.sh > foo.out 2> foo.err < /dev/null &

Spring Boot在尝试创建应用程序上下文时提供了几个应用程序侦听器,其中一个是ApplicationFailedEvent。我们可以使用它来了解应用程序上下文是否初始化

    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.boot.context.event.ApplicationFailedEvent; 
    import org.springframework.context.ApplicationListener;

    public class ApplicationErrorListener implements 
                    ApplicationListener<ApplicationFailedEvent> {

        private static final Logger LOGGER = 
        LoggerFactory.getLogger(ApplicationErrorListener.class);

        @Override
        public void onApplicationEvent(ApplicationFailedEvent event) {
           if (event.getException() != null) {
                LOGGER.info("!!!!!!Looks like something not working as 
                                expected so stoping application.!!!!!!");
                         event.getApplicationContext().close();
                  System.exit(-1);
           } 
        }
    }

从Spring Boot 2.3及更高版本开始,有一个内置机制

Pre-Spring Boot 2.3,没有现成的优雅关机机制。 一些spring引导启动器提供以下功能:

  • 我是第一册的作者。起动器被命名为“弹簧启动中断”。它在负载平衡器级别上工作,即简单地将服务标记为OUT OF_service,而不以任何方式干扰应用程序上下文。这允许进行优雅的关机,意味着如果需要,服务可以停止使用一段时间,然后恢复正常。缺点是它不会停止JVM,您必须使用
    kill
    命令来停止JVM。当我在容器中运行所有内容时,这对我来说没什么大不了的,因为无论如何我都必须停止并移除容器


    第2号和第3号或多或少是基于安迪·威尔金森的作品。它们是单向工作的——一旦被触发,它们最终会关闭上下文。

    SpringApplication会向JVM隐式注册一个关闭钩子,以确保ApplicationContext在退出时正常关闭。它还将调用所有用
    @PreDestroy
    注释的bean方法。这意味着我们不必像在spring core应用程序中那样,在启动应用程序中显式使用
    ConfigurableApplicationContext
    RegisterShotDownhook()
    方法

        new SpringApplicationBuilder(Application.class)
                .listeners(new ApplicationErrorListener())
                .run(args);  
    
    @SpringBootConfiguration
    public class ExampleMain {
        @Bean
        MyBean myBean() {
            return new MyBean();
        }
    
        public static void main(String[] args) {
            ApplicationContext context = SpringApplication.run(ExampleMain.class, args);
            MyBean myBean = context.getBean(MyBean.class);
            myBean.doSomething();
    
            //no need to call context.registerShutdownHook();
        }
    
        private static class MyBean {
    
            @PostConstruct
            public void init() {
                System.out.println("init");
            }
    
            public void doSomething() {
                System.out.println("in doSomething()");
            }
    
            @PreDestroy
            public void destroy() {
                System.out.println("destroy");
            }
        }
    }
    

    所有答案似乎都忽略了一个事实,即在正常关机期间(例如,在企业应用程序中),您可能需要以协调的方式完成部分工作

    @PreDestroy
    允许您在单个bean中执行关闭代码。更复杂的东西看起来像这样:

    @Component
    public class ApplicationShutdown implements ApplicationListener<ContextClosedEvent> {
         @Autowired ... //various components and services
    
         @Override
         public void onApplicationEvent(ContextClosedEvent event) {
             service1.changeHeartBeatMessage(); // allows loadbalancers & clusters to prepare for the impending shutdown
             service2.deregisterQueueListeners();
             service3.finishProcessingTasksAtHand();
             service2.reportFailedTasks();
             service4.gracefullyShutdownNativeSystemProcessesThatMayHaveBeenLaunched(); 
             service1.eventLogGracefulShutdownComplete();
         }
    }
    
    @组件
    公共类ApplicationShutton实现ApplicationListener{
    @自动连线…//各种组件和服务
    @凌驾
    Application event(ContextClosedEvent事件)上的公共无效{
    service1.changeHeartBeatMessage();//允许负载平衡器和集群为即将到来的关机做好准备
    service2.deregisterQueueListeners();
    service3.finishProcessingTasksAtHand();
    service2.reportFailedTasks();
    服务4.优雅地关闭可能已启动的本地系统进程();
    service1.EventLogGracefulShutdownPlete();
    }
    }
    
    如果您在linux环境中,您所要做的就是从/etc/init.d/内部创建指向.jar文件的符号链接

    sudo ln -s /path/to/your/myboot-app.jar /etc/init.d/myboot-app
    
    然后,您可以像启动任何其他服务一样启动应用程序

    sudo /etc/init.d/myboot-app start
    
    关闭应用程序

    sudo /etc/init.d/myboot-app stop
    
    这样,当您退出终端时,应用程序将不会终止。应用程序将使用stop命令正常关闭

    server.shutdown.grace-period=30s
    
    @Component
    public class AppShutdownHook implements ApplicationListener<ContextClosedEvent> {
    
        private static final Logger logger = LoggerFactory.getLogger(AppShutdownHook.class);
    
    
        @Override
        public void onApplicationEvent(ContextClosedEvent event) {
            logger.info("shutdown requested !!!");
            try {
                //TODO Add logic to shutdown, diff elements of your application
                } catch (Exception e) {
                logger.error("Exception occcured while shutting down Application:", e);
            }
    
        }
    
    
    }