Обычно в проекте Springboot их несколько.
CommandLineRunner
Класс реализации используется для выполнения каких-либо действий после запуска программы. Но при неправильном использовании вы можете обнаружить, что некоторые Раннеры выполняются после запуска программы, а некоторые не выполняются, что еще более странно, программа не сообщает об ошибке. Причина в том, что...
первый взгляд на явление
Предположим, что проекту необходимо использовать 3 CommandLineRunners, 2 из которых должны выполнять цикл while(true)
код показывает, как показано ниже:
@Component
public class Runner1 implements CommandLineRunner {
public void run(String... args) throws Exception {
log.info("runner 1 running...");
}
}
@Component
public class Runner2 implements CommandLineRunner {
public void run(String... args) throws Exception {
log.info("runner 2 running...")
while(true) {
doSomething();
}
}
}
@Component
public class Runner3 implements CommandLineRunner {
public void run(String... args) throws Exception {
log.info("runner 3 running...")
while(true) {
doOtherThing();
}
}
}
После запуска программы вы обнаружите, что ни один из Runner2 и Runner3 не запустится, даже Runner1 не запустится, но программа не сообщит об ошибке, что происходит?
Видение явлений сквозь сущность
да, посмотри сначалаCommandLineRunner
Как это реализовано, чтобы узнать, где ошибка и решить проблему.
CommandLineRunner.java
/**
* Interface used to indicate that a bean should <em>run</em> when it is contained within
* a {@link SpringApplication}. Multiple {@link CommandLineRunner} beans can be defined
* within the same application context and can be ordered using the {@link Ordered}
* interface or {@link Order @Order} annotation.
* <p>
* If you need access to {@link ApplicationArguments} instead of the raw String array
* consider using {@link ApplicationRunner}.
*
* @author Dave Syer
* @since 1.0.0
* @see ApplicationRunner
*/
@FunctionalInterface
public interface CommandLineRunner {
/**
* Callback used to run the bean.
* @param args incoming main method arguments
* @throws Exception on error
*/
void run(String... args) throws Exception;
}
Первое предложение комментария означает: bean-компонент Spring, реализующий этот интерфейс, должен запускать программуrun
.
Здесь используется бег<em>
Метки выделены, почему? Не спешите отвечать, посмотрите вниз, чтобы увидеть, как CommandLineRunner выполняется Spring Boot...
SpringApplication.java
см. далееSpringApplication
Информация аннотации для этого класса:
* Class that can be used to bootstrap and launch a Spring application from a Java main
* method. By default class will perform the following steps to bootstrap your
* application:
*
* <ul>
* <li>Create an appropriate {@link ApplicationContext} instance (depending on your
* classpath)</li>
* <li>Register a {@link CommandLinePropertySource} to expose command line arguments as
* Spring properties</li>
* <li>Refresh the application context, loading all singleton beans</li>
* <li>Trigger any {@link CommandLineRunner} beans</li>
* </ul>
Из этой заметки видно, чтоSpringApplication
отвечает за запуск всехCommandLineRunner
операция. Для части кода это следующие методы:
- callRunners()
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List<Object> runners = new ArrayList<>();
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
AnnotationAwareOrderComparator.sort(runners);
for (Object runner : new LinkedHashSet<>(runners)) {
if (runner instanceof ApplicationRunner) {
callRunner((ApplicationRunner) runner, args);
}
if (runner instanceof CommandLineRunner) {
callRunner((CommandLineRunner) runner, args);
}
}
}
- callRunner()
private void callRunner(ApplicationRunner runner, ApplicationArguments args) {
try {
(runner).run(args);
}
catch (Exception ex) {
throw new IllegalStateException("Failed to execute ApplicationRunner", ex);
}
}
private void callRunner(CommandLineRunner runner, ApplicationArguments args) {
try {
(runner).run(args.getSourceArgs());
}
catch (Exception ex) {
throw new IllegalStateException("Failed to execute CommandLineRunner", ex);
}
}
callRunners отвечает за получение всех классов реализации CommandLineRunner из контекста, обход в цикле и вызов метода callRunner для срабатывания для каждого объекта класса реализации. И в методе callRunnerСинхронизироватьВыполняется метод run объекта бегуна.
На данный момент ответ очень ясен, CommandLineRunner не запускается, или некоторые запускаются, некоторые не запускаются, причина, по которой программа не сообщает об ошибке:
- У вас есть несколько реализаций CommandLineRunner
- API синхронной блокировки или цикл while(true) вызывается в теле метода run класса реализации.
Это приводит к тому, что SpringApplication блокируется при выполнении этого метода запуска и не может продолжать выполнять метод запуска следующего исполнителя в цикле.
Чтобы избежать этой ошибки в вашей программе, вы должны помнить,
CommandLineRunner != Runnable
CommandLineRunner != Runnable
CommandLineRunner != Runnable
Хотя оба интерфейса имеют метод run, легко понять, что CommandLineRunner соответствует потоку. НЕТ!
Измените его, когда вы знаете, что это неправильно
Никаких модификаций для Runner1 не требуется. Для Runner2 и Runner3 необходимо внести следующие изменения, чтобы цикл while(true) не блокировал выполнение метода run.
@Component
public class Runner2 implements CommandLineRunner {
public void run(String... args) throws Exception {
log.info("runner 2 running...")
new Thread(() -> {
while(true) {
doSomething();
}
}).start();
}
}
@Component
public class Runner3 implements CommandLineRunner {
public void run(String... args) throws Exception {
log.info("runner 3 running...")
new Thread(() -> {
while(true) {
doOtherThing();
}
}).start();
}
}