¿Cómo cerrar una aplicación Spring Boot de forma correcta?

En el Documento de arranque de Spring, dijeron que ‘Cada SpringApplication registrará un gancho de cierre con la JVM para garantizar que ApplicationContext se cierra correctamente al salir.’

Cuando hago clic en ctrl+c en el comando de shell, la aplicación se puede cerrar correctamente. Si ejecuto la aplicación en una máquina de producción, tengo que usar el comando java -jar ProApplicaton.jar . Pero no puedo cerrar el terminal de shell, de lo contrario cerrará el proceso.

Si ejecuto el comando como nohup java -jar ProApplicaton.jar & , no puedo usar ctrl+c para cerrarlo con gracia.

¿Cuál es la forma correcta de iniciar y detener una aplicación Spring Boot en el entorno de producción?

Si está utilizando el módulo de actuador, puede cerrar la aplicación a través de JMX o HTTP si el punto final está habilitado (agregue endpoints.shutdown.enabled=true a su archivo application.properties ).

/shutdown : permite que la aplicación se cierre correctamente (no está habilitada de forma predeterminada).

Dependiendo de cómo se expone un punto final, el parámetro sensible se puede usar como una pista de seguridad. Por ejemplo, los puntos finales sensibles requerirán un nombre de usuario / contraseña cuando se acceda a través de HTTP (o simplemente se deshabilitará si la seguridad web no está habilitada).

De la documentación de arranque de spring

En cuanto a la respuesta de @Jean-Philippe Bond,

Aquí hay un ejemplo rápido de maven para que el usuario maven configure el punto final HTTP para apagar una aplicación web de arranque de spring usando spring-boot-starter-actuator para que pueda copiar y pegar:

1.Maven pom.xml:

  org.springframework.boot spring-boot-starter-actuator  

2.application.properties:

 #No auth protected endpoints.shutdown.sensitive=false #Enable shutdown endpoint endpoints.shutdown.enabled=true 

Todos los puntos finales se enumeran aquí :

3. Enviar un método de publicación para cerrar la aplicación:

 curl -X POST localhost:port/shutdown 

Nota de seguridad:

si necesita el método de apagado auth protected, también puede necesitar

  org.springframework.boot spring-boot-starter-security  

configurar detalles :

Puede hacer que la aplicación Springboot escriba el PID en un archivo y puede usar el archivo pid para detener o reiniciar o obtener el estado usando un script bash. Para escribir el PID en un archivo, registre un oyente en SpringApplication usando ApplicationPidFileWriter como se muestra a continuación:

 SpringApplication application = new SpringApplication(Application.class); application.addListeners(new ApplicationPidFileWriter("./bin/app.pid")); application.run(); 

Luego escribe un script bash para ejecutar la aplicación de inicio de spring. Referencia .

Ahora puede usar la secuencia de comandos para iniciar, detener o reiniciar.

Aquí hay otra opción que no requiere que cambie el código ni exhiba un punto final de apagado. Cree los siguientes scripts y úselos para iniciar y detener su aplicación.

start.sh

 #!/bin/bash java -jar myapp.jar & echo $! > ./pid.file & 

Inicia su aplicación y guarda la identificación del proceso en un archivo

stop.sh

 #!/bin/bash kill $(cat ./pid.file) 

Detiene tu aplicación usando la ID del proceso guardado

start_silent.sh

 #!/bin/bash nohup ./start.sh > foo.out 2> foo.err < /dev/null & 

Si necesita iniciar la aplicación utilizando ssh desde una máquina remota o una interconexión de CI, entonces use esta secuencia de comandos para iniciar su aplicación. Usando start.sh directamente puede dejar el shell para colgar.

Después de ej. Al volver a implementar su aplicación, puede reiniciarla usando:

 sshpass -p password ssh -oStrictHostKeyChecking=no userName@www.domain.com 'cd /home/user/pathToApp; ./stop.sh; ./silent_start.sh' 

Spring Boot proporcionó varios escuchas de aplicaciones mientras intentaba crear el contexto de la aplicación, uno de ellos es ApplicationFailedEvent. Podemos utilizar para saber si el contexto de la aplicación se inicializó o no.

  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 { 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); } } } 

Agregar a la clase de escucha anterior a SpringApplication.

  new SpringApplicationBuilder(Application.class) .listeners(new ApplicationErrorListener()) .run(args); 

No expongo ningún punto final y comienzo ( con nohup en segundo plano y sin archivos creados a través de nohup ) y detengo el script de shell (con KILL PID correctamente y fuerzo a matar si la aplicación aún se está ejecutando después de 3 minutos ). Simplemente creo el archivo jar ejecutable y uso el escritor de archivos PID para escribir el archivo PID y almacenar Jar y Pid en la carpeta con el mismo nombre que el nombre de la aplicación y los guiones del intérprete de órdenes también tienen el mismo nombre con inicio y fin al final. Llamo a estos stop script y start script vía jenkins pipeline también. No hay problemas hasta el momento. Perfectamente funcional para 8 aplicaciones (scripts muy generics y fáciles de aplicar para cualquier aplicación).

Clase principal

 @SpringBootApplication public class MyApplication { public static final void main(String[] args) { SpringApplicationBuilder app = new SpringApplicationBuilder(MyApplication.class); app.build().addListeners(new ApplicationPidFileWriter()); app.run(); } } 

ARCHIVO YML

 spring.pid.fail-on-write-error: true spring.pid.file: /server-path-with-folder-as-app-name-for-ID/appName/appName.pid 

Aquí está el script de inicio (start-appname.sh):

 shellScriptFileName=$(basename -- "$0") shellScriptFileNameWithoutExt="${shellScriptFileName%.*}" appName=${shellScriptFileNameWithoutExt:6} PROCESS=$1 PIDS=`ps aux |grep [j]ava.*$appName.*jar | awk {'print $2'}` if [ -z "$PIDS" ]; then echo "No instances of $appName is running..." 1>&2 else for PID in $PIDS; do echo "Waiting for the process($PID) to finish on it's own for 3 mins..." sleep 3m echo "FATAL:Killing $appName with PID:$PID." kill -9 $PID done fi # Preparing the java home path for execution JAVA_EXEC='/usr/bin/java' # Java Executable - Jar Path Obtained from latest file in directory JAVA_APP=$(ls -t /server-path-with-folder-as-app-name/$appName/$appName*.jar | head -n1) # JVM Parameters and Spring boot initialization parameters JVM_PARAM="-Xms512m -Xmx1024m -Dspring.profiles.active=sit -Dcom.webmethods.jms.clientIDSharing=true" # To execute the application. FINAL_EXEC="$JAVA_EXEC $JVM_PARAM -jar $JAVA_APP" # Making executable command using tilde symbol and running completely detached from terminal `nohup $FINAL_EXEC /dev/null 2>&1 &` echo "$appName has been started successfully." 

Aquí está el script stop (stop-appname.sh):

 shellScriptFileName=$(basename -- "$0") shellScriptFileNameWithoutExt="${shellScriptFileName%.*}" appName=${shellScriptFileNameWithoutExt:5} # Script to stop the application PID_PATH="server-path-with-folder-as-app-name-for-PID/$appName/$appName.pid" if [ ! -f "$PID_PATH" ]; then echo "Process Id FilePath($PID_PATH) Not found" else pid=`cat $PID_PATH` if [ ! -e /proc/$pid -a /proc/$pid/exe ]; then echo "$appName was not running."; else kill $pid; echo "Gracefully stopping $appName with PID:$pid..." fi fi 

A partir de Spring Boot 1.5, no existe un mecanismo de apagado elegante fuera de la caja. Algunos arrancadores de arranque de spring proporcionan esta funcionalidad:

  1. https://github.com/jihor/hiatus-spring-boot
  2. https://github.com/gesellix/graceful-shutdown-spring-boot
  3. https://github.com/corentin59/spring-boot-graceful-shutdown

Soy el autor de nr. 1. El motor de arranque se llama “Hiatus for Spring Boot”. Funciona en el nivel del equilibrador de carga, es decir, simplemente marca el servicio como OUT_OF_SERVICE, sin interferir con el contexto de la aplicación de ninguna manera. Esto permite un cierre elegante y significa que, si es necesario, el servicio puede dejar de funcionar por un tiempo y luego volver a la vida. Lo malo es que no detiene la JVM, tendrás que hacerlo con el comando kill . Como manejo todo en contenedores, esto no fue gran cosa para mí, porque de todos modos tendré que detenerme y retirar el contenedor.

Los números 2 y 3 están más o menos basados ​​en este post de Andy Wilkinson. Funcionan en una sola dirección: una vez que se activan, eventualmente cierran el contexto.

SpringApplication registra implícitamente un enlace de cierre con la JVM para garantizar que ApplicationContext se cierra correctamente al salir. Eso también llamará a todos los métodos de bean anotados con @PreDestroy . Eso significa que no tenemos que usar explícitamente el método registerShutdownHook() de un ConfigurableApplicationContext en una aplicación de arranque, como tenemos que hacer en la aplicación de Spring Core.

 @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"); } } } 

A todas las respuestas parece faltar el hecho de que es posible que deba completar una parte del trabajo de forma coordinada durante el apagado ordenado (por ejemplo, en una aplicación empresarial).

@PreDestroy permite ejecutar el código de apagado en los beans individuales. Algo más sofisticado se vería así:

 @Component public class ApplicationShutdown implements ApplicationListener { @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(); } } 

Si está usando maven, puede usar el plugin Maven App assembler .

El daemon mojo (que incorpora JSW ) generará un script de shell con el argumento de inicio / detención. La stop apagará / matará con gracia su aplicación Spring.

El mismo script se puede usar para usar su aplicación Maven como un servicio de Linux.

Si estás en un entorno Linux, todo lo que tienes que hacer es crear un enlace simbólico a tu archivo .jar desde /etc/init.d/

 sudo ln -s /path/to/your/myboot-app.jar /etc/init.d/myboot-app 

Entonces puede iniciar la aplicación como cualquier otro servicio

 sudo /etc/init.d/myboot-app start 

Para cerrar la aplicación

 sudo /etc/init.d/myboot-app stop 

De esta manera, la aplicación no terminará cuando salga de la terminal. Y la aplicación se cerrará correctamente con el comando de detención.

Un contexto de aplicación de spring puede haber registrado un enganche de cierre con el tiempo de ejecución de JVM. Ver la documentación de ApplicationContext .

No lo hago si Spring Boot configura este gancho automáticamente como dijiste. Si suponemos que este enlace está configurado correctamente, su pregunta es “Cómo cerrar correctamente la JVM”.

Para hacer eso, Ctrl+C funciona muy bien, incluso si usa nohup . Más explicación:

En Ctrl+C , tu shell envía una señal INT a la aplicación de primer plano. Significa “por favor interrumpa su ejecución”. La aplicación puede atrapar esta señal y hacer una limpieza antes de su finalización, o simplemente ignorarla (mal).

nohup es un comando que ejecuta el siguiente progtwig con una trampa para ignorar la señal HUP. HUP se utiliza para finalizar el progtwig cuando cuelgas (cierra tu conexión ssh por ejemplo). Además, redirige los resultados para evitar que el progtwig bloquee un TTY desaparecido.

Supongo que su problema es causado por el símbolo comercial (&), no por nohup. Ctrl+C envía una señal a los procesos en primer plano. El signo común hace que su aplicación se ejecute en segundo plano. Una solución: hacer

 kill -INT pid 

Use kill -9 o kill -KILL es malo porque la aplicación (aquí la JVM) no puede atraparlo para terminar con gracia.

Otra solución es traer de vuelta su aplicación en primer plano. Entonces Ctrl+C funcionará. Eche un vistazo al control Bash Job, más precisamente en fg .