Как закрыть загрузочное приложение Spring правильно?

В загрузочном документе Spring они сказали, что "Каждая SpringApplication зарегистрирует крюк отключения с помощью JVM, чтобы гарантировать, что ApplicationContext будет законно закрыт при выходе".

Когда я нажимаю ctrl+c в командной строке, приложение может быть отключено изящно. Если я запускаю приложение на производственной машине, я должен использовать команду java -jar ProApplicaton.jar. Но я не могу закрыть терминал оболочки, иначе он закроет процесс.

Если я запускаю команду как nohup java -jar ProApplicaton.jar &, я не могу использовать ctrl+c для изящного изложения.

Каков правильный способ запуска и остановки загрузочного приложения Spring в рабочей среде?

Ответ 1

Если вы используете модуль привода, вы можете отключить приложение через JMX или HTTP, если конечная точка включена (добавьте endpoints.shutdown.enabled=true в ваш файл application.properties).

/shutdown - Позволяет корректно отключать приложение (не включено по умолчанию).

В зависимости от того, как отображается конечная точка, чувствительный параметр может использоваться в качестве подсказки безопасности. Например, для чувствительных конечных точек потребуется имя пользователя/пароль при обращении через HTTP (или просто отключено, если веб-безопасность не включена).

Из Spring загрузочной документации

Ответ 2

Что касается ответа @Jean-Philippe Bond,

Вот пример maven для пользователя maven для настройки конечной точки HTTP для отключения загрузочного веб-приложения spring с помощью spring -boot-starter-actuator, чтобы вы могли копировать и вставлять:

1.Maven pom.xml:

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

2.application.properties:

#No auth  protected 
endpoints.shutdown.sensitive=false

#Enable shutdown endpoint
endpoints.shutdown.enabled=true

Все конечные точки перечислены здесь:

3.Отправить почтовый метод для отключения приложения:

curl -X POST localhost:port/shutdown

Примечание по безопасности:

если вам нужен метод shutdown auth protected, вам также может понадобиться

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

сконфигурировать данные:

Ответ 3

Вот еще один вариант, который не требует от вас изменения кода или предоставления конечной точки выключения. Создайте следующие сценарии и используйте их для запуска и остановки вашего приложения.

start.sh

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

Запускает ваше приложение и сохраняет идентификатор процесса в файле

stop.sh

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

Останавливает ваше приложение, используя сохраненный идентификатор процесса

start_silent.sh

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

Если вам нужно запустить приложение, используя ssh с удаленной машины или конвейера CI, используйте этот скрипт вместо этого, чтобы запустить ваше приложение. Использование start.sh напрямую может привести к зависанию оболочки.

После например Для повторного развертывания приложения вы можете перезапустить его, используя:

sshpass -p password ssh -oStrictHostKeyChecking=no [email protected] 'cd /home/user/pathToApp; ./stop.sh; ./start_silent.sh'

Ответ 4

Вы можете сделать приложение springboot для записи PID в файл, и вы можете использовать файл pid для остановки или перезапуска или получения статуса с помощью bash script. Чтобы записать PID в файл, зарегистрируйте прослушиватель SpringApplication с помощью ApplicationPidFileWriter, как показано ниже:

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

Затем напишите bash script, чтобы запустить приложение загрузки spring. Ссылка.

Теперь вы можете использовать script для запуска, остановки или перезапуска.

Ответ 5

Я не выставляю никакие конечные точки и запускаю (с nohup в фоновом режиме и без файлов out, созданных через nohup) и останавливаюсь с помощью сценария оболочки (с изящным KILL PID и принудительным уничтожением, если приложение все еще работает через 3 минуты). Я просто создаю исполняемый jar и использую PID файл для записи PID файла и сохраняю Jar и Pid в папке с тем же именем, что и у имени приложения, а сценарии оболочки также имеют одинаковые имена с start и stop в конце. Я вызываю эти сценарии остановки и запускаю сценарий через конвейер jenkins. Пока никаких проблем. Идеально работает для 8 приложений (очень универсальные сценарии и легко применяются для любого приложения).

Основной класс

@SpringBootApplication
public class MyApplication {

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

YML ФАЙЛ

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

Вот стартовый скрипт (start-appname.sh):

#Active Profile(YAML)
ACTIVE_PROFILE="preprod"
# JVM Parameters and Spring boot initialization parameters
JVM_PARAM="-Xms512m -Xmx1024m -Dspring.profiles.active=${ACTIVE_PROFILE} -Dcom.webmethods.jms.clientIDSharing=true"
# Base Folder Path like "/folder/packages"
CURRENT_DIR=$(readlink -f "$0")
BASE_PACKAGE="${CURRENT_DIR%/bin/*}"
# Shell Script file name after removing path like "start-yaml-validator.sh"
SHELL_SCRIPT_FILE_NAME=$(basename -- "$0")
# Shell Script file name after removing extension like "start-yaml-validator"
SHELL_SCRIPT_FILE_NAME_WITHOUT_EXT="${SHELL_SCRIPT_FILE_NAME%.sh}"
# App name after removing start/stop strings like "yaml-validator"
APP_NAME=${SHELL_SCRIPT_FILE_NAME_WITHOUT_EXT#start-}

PIDS='ps aux |grep [j]ava.*-Dspring.profiles.active=$ACTIVE_PROFILE.*$APP_NAME.*jar | awk {'print $2'}'
if [ -z "$PIDS" ]; then
  echo "No instances of $APP_NAME with profile:$ACTIVE_PROFILE is running..." 1>&2
else
  for PROCESS_ID in $PIDS; do
        echo "Please stop the process($PROCESS_ID) using the shell script: stop-$APP_NAME.sh"
  done
  exit 1
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 $BASE_PACKAGE/apps/$APP_NAME/$APP_NAME*.jar | head -n1)
# 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 >/dev/null 2>&1 &'
echo "$APP_NAME start script is  completed."

Вот скрипт остановки (stop-appname.sh):

#Active Profile(YAML)
ACTIVE_PROFILE="preprod"
#Base Folder Path like "/folder/packages"
CURRENT_DIR=$(readlink -f "$0")
BASE_PACKAGE="${CURRENT_DIR%/bin/*}"
# Shell Script file name after removing path like "start-yaml-validator.sh"
SHELL_SCRIPT_FILE_NAME=$(basename -- "$0")
# Shell Script file name after removing extension like "start-yaml-validator"
SHELL_SCRIPT_FILE_NAME_WITHOUT_EXT="${SHELL_SCRIPT_FILE_NAME%.*}"
# App name after removing start/stop strings like "yaml-validator"
APP_NAME=${SHELL_SCRIPT_FILE_NAME_WITHOUT_EXT:5}

# Script to stop the application
PID_PATH="$BASE_PACKAGE/config/$APP_NAME/$APP_NAME.pid"

if [ ! -f "$PID_PATH" ]; then
   echo "Process Id FilePath($PID_PATH) Not found"
else
    PROCESS_ID='cat $PID_PATH'
    if [ ! -e /proc/$PROCESS_ID -a /proc/$PROCESS_ID/exe ]; then
        echo "$APP_NAME was not running with PROCESS_ID:$PROCESS_ID.";
    else
        kill $PROCESS_ID;
        echo "Gracefully stopping $APP_NAME with PROCESS_ID:$PROCESS_ID..."
        sleep 5s
    fi
fi
PIDS='/bin/ps aux |/bin/grep [j]ava.*-Dspring.profiles.active=$ACTIVE_PROFILE.*$APP_NAME.*jar | /bin/awk {'print $2'}'
if [ -z "$PIDS" ]; then
  echo "All instances of $APP_NAME with profile:$ACTIVE_PROFILE has has been successfully stopped now..." 1>&2
else
  for PROCESS_ID in $PIDS; do
    counter=1
    until [ $counter -gt 150 ]
        do
            if ps -p $PROCESS_ID > /dev/null; then
                echo "Waiting for the process($PROCESS_ID) to finish on it own for $(( 300 - $(( $counter*5)) ))seconds..."
                sleep 2s
                ((counter++))
            else
                echo "$APP_NAME with PROCESS_ID:$PROCESS_ID is stopped now.."
                exit 0;
            fi
    done
    echo "Forcefully Killing $APP_NAME with PROCESS_ID:$PROCESS_ID."
    kill -9 $PROCESS_ID
  done
fi

Ответ 6

Spring Загрузите несколько прослушивателей приложений при попытке создать контекст приложения, один из которых - 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);
           } 
        }
    }

Добавьте к классу слушателя выше значение SpringApplication.

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

Ответ 7

SpringApplication неявно регистрирует крюк остановки с помощью JVM, чтобы гарантировать, что ApplicationContext будет закрыт грациозно при выходе. Это также вызовет все методы bean, аннотированные с помощью @PreDestroy. Это означает, что нам не нужно явно использовать метод registerShutdownHook() ConfigurableApplicationContext в загрузочном приложении, как мы должны делать в весеннем ключевом приложении.

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

Ответ 8

Кажется, что на всех ответах отсутствует тот факт, что при грациозном завершении работы (например, в корпоративном приложении) вам может потребоваться скоординировать работу некоторой части работы.

@PreDestroy позволяет вам выполнить код выключения в отдельных компонентах. Что-то более сложное выглядело бы так:

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

Ответ 9

Как и в случае с Spring Boot 1.5, нет изящного механизма отключения. Некоторые spring -boot стартеры предоставляют эту функциональность:

Я автор nr. 1. Стартер называется "Hiatus для Spring Boot". Он работает на уровне балансировки нагрузки, т.е. Просто отмечает службу как OUT_OF_SERVICE, никак не мешая контексту приложения. Это позволяет сделать изящное закрытие и означает, что при необходимости услуга может быть выведена из эксплуатации в течение некоторого времени, а затем возвращена к жизни. Недостатком является то, что он не останавливает JVM, вам нужно будет сделать это с помощью команды kill. Поскольку я запускаю все в контейнерах, для меня это не было большим делом, потому что мне все равно придется остановиться и удалить контейнер.

пп. 2 и 3 более или менее основаны на этот пост Энди Уилкинсона. Они работают в одну сторону - как только они запускаются, они в конечном итоге закрывают контекст.

Ответ 10

У них есть много способов закрыть приложение весны. Одним из них является вызов close() для ApplicationContext:

ApplicationContext ctx =
    SpringApplication.run(HelloWorldApplication.class, args);
// ...
ctx.close()

Ваш вопрос предполагает, что вы хотите закрыть свое приложение, нажав Ctrl+C, который часто используется для завершения команды. В этом случае...

Используйте endpoints.shutdown.enabled=true не лучший рецепт. Это означает, что вы предоставляете конечную точку для завершения вашего приложения. Таким образом, в зависимости от вашего варианта использования и вашей среды, вам придется обеспечить его...

Ctrl+C должен работать очень хорошо в вашем случае. Я предполагаю, что ваша проблема вызвана амперсандом (&). Больше объяснений:

В контексте приложения Spring может быть зарегистрирован хук отключения во время выполнения JVM. Смотрите документацию ApplicationContext.

Я не знаю, настроил ли Spring Boot этот хук автоматически, как вы сказали. Я предполагаю, что это так.

При Ctrl+C ваша оболочка отправляет сигнал INT в приложение переднего плана. Это означает "пожалуйста, прервите казнь". Приложение может перехватить этот сигнал и выполнить очистку до его завершения (хук, зарегистрированный Spring), или просто проигнорировать его (плохо).

nohup - команда, которая выполняет следующую программу с ловушкой, чтобы игнорировать сигнал HUP. HUP используется для завершения программы, когда вы кладете трубку (например, закрывайте ssh-соединение). Более того, он перенаправляет выходные данные, чтобы ваша программа не блокировала пропавший TTY. nohup НЕ игнорирует сигнал INT. Так что это не мешает Ctrl+C работать.

Я предполагаю, что ваша проблема вызвана амперсандом (&), а не nohup. Ctrl+C отправляет сигнал на передний план процессов. Амперсанд заставляет ваше приложение работать в фоновом режиме. Одно из решений: сделать

kill -INT pid

Использовать kill -9 или kill -KILL плохо, потому что приложение (здесь JVM) не может перехватить его для kill -KILL завершения.

Другое решение - вернуть ваше приложение на передний план. Тогда Ctrl+C будет работать. Взгляните на управление Bash Job, точнее на fg.

Ответ 11

Если вы используете maven, вы можете использовать плагин ассемблера Maven App.

Демон mojo (который вставляет JSW) будет выведите оболочку script с аргументом start/stop. stop будет корректно завершать/убивать ваше приложение Spring.

Тот же script может использоваться для использования вашего приложения maven в качестве службы linux.

Ответ 12

Если вы находитесь в среде Linux, все, что вам нужно сделать, это создать символическую ссылку на ваш.jar файл из /etc/init.d/

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.

Ответ 13

Используйте статический метод exit() в классе SpringApplication для изящного закрытия приложения весенней загрузки.

public class SomeClass {
    @Autowire
    private ApplicationContext context

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

Ответ 14

Запустите/запустите развертывание с помощью Spring Boot Maven Plugin

mvn spring-boot:run

и остановка/выключение с

Ctrl+C