Как закрыть загрузочное приложение 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