[SpringBoot] Почему мой CommandLineRunner не запускается?

Spring Boot

Обычно в проекте 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 не запускается, или некоторые запускаются, некоторые не запускаются, причина, по которой программа не сообщает об ошибке:

  1. У вас есть несколько реализаций CommandLineRunner
  2. 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();
    }
}