Множественная загрузка Spring CommandLineRunner на основе аргумента командной строки

Я создал весеннее загрузочное приложение с весенней облачной задачей, которая должна выполнять несколько команд (задач). Каждая задача/команда является краткосрочной задачей, и все задачи запускаются из командной строки, выполняют какое-то короткое задание ETL и заканчивают выполнение.

Существует одна загрузочная банка Spring, которая содержит все команды/задачи. Каждая задача — это CommandLineRunner, и мне нравится решать, какие задачи (одна или несколько) будут выполняться на основе параметров из командной строки. Как лучше всего это сделать? Мне не нравится иметь грязный код, который спрашивает «если еще» или что-то в этом роде.


person Shay    schedule 11.06.2017    source источник
comment
Если у вас есть несколько основных классов в файле jar, вы можете ввести java -classpath myapp.jar com.example.Task1 в командной строке вместо java -jar myapp.jar. Нет если-ничего нигде не видно. Почему ненависть к if-else? Программа должна разветвляться несколько раз.   -  person Barend    schedule 11.06.2017
comment
Спасибо, я не уверен, что весенняя загрузка позволяет использовать несколько основных классов. Использование жестко запрограммированного if-else сложнее поддерживать, чем вводить компоненты.   -  person Shay    schedule 12.06.2017


Ответы (4)


Вы также можете сделать свои реализации CommandLineRunner @Component и @ConditionalOnExpression("${someproperty:false}")

затем иметь несколько профилей, в которых для некоторых свойств установлено значение true, чтобы включить эти CommandLineRunners в контекст.

@Component
@Slf4j
@ConditionalOnExpression("${myRunnerEnabled:false}")
public class MyRunner implements CommandLineRunner {
    @Override
    public void run(String ... args) throws Exception {
        log.info("this ran");
    }
}

а в приложении yml-myrunner.yml

myRunnerEnabled: true
@SpringBootApplication
public class SpringMain {
    public static void main(String ... args) {
        SpringApplication.run(SpringMain.class, args);
    }
}
person user2043566    schedule 12.03.2019
comment
Это элегантное решение, когда приложение и другой зависимый артефакт реализуют CommandLineRunner. Без этого решения выполняются оба класса реализации. - person Srikanta; 30.10.2019

Spring Boot запускает все bean-компоненты CommandLineRunner или ApplicationRunner из контекста приложения. Вы не можете выбрать один по каким-либо аргументам.

Итак, в основном у вас есть две возможности:

  1. У вас есть разные реализации CommandLineRunner, и в каждой из них вы проверяете аргументы, чтобы определить, должен ли работать этот специальный CommandLineRunner.
  2. Вы реализуете только один CommandLineRunner, который действует как диспетчер. Код может выглядеть примерно так:

Это новый интерфейс, который будут реализовывать ваши бегуны:

public interface MyCommandLineRunner {
    void run(String... strings) throws Exception;
}

Затем вы определяете реализации и идентифицируете их по имени:

@Component("one")
public class MyCommandLineRunnerOne implements MyCommandLineRunner {
    private static final Logger log = LoggerFactory.getLogger(MyCommandLineRunnerOne.class);

    @Override
    public void run(String... strings) throws Exception {
        log.info("running");
    }
}

и

@Component("two")
public class MyCommandLineRunnerTwo implements MyCommandLineRunner {
    private static final Logger log = LoggerFactory.getLogger(MyCommandLineRunnerTwo.class);
    @Override
    public void run(String... strings) throws Exception {
        log.info("running");
    }
}

Затем в вашей единственной реализации CommandLineRunner вы получаете контекст приложения и разрешаете требуемый bean-компонент по имени, в моем примере используется только первый аргумент и вызывается MyCommandLineRunner.run()method:

@Component
public class CommandLineRunnerImpl implements CommandLineRunner, ApplicationContextAware {
    private ApplicationContext applicationContext;


    @Override
    public void run(String... strings) throws Exception {
        if (strings.length < 1) {
            throw new IllegalArgumentException("no args given");
        }

        String name = strings[0];
        final MyCommandLineRunner myCommandLineRunner = applicationContext.getBean(name, MyCommandLineRunner.class);
        myCommandLineRunner.run(strings);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}
person P.J.Meisch    schedule 11.06.2017
comment
Мы пытаемся реализовать это с помощью аннотации CliCommand команды spring cli. Кажется, что каждая команда похожа на основную, которая уже анализирует аргументы для командной строки. - person Shay; 12.06.2017
comment
так что это нечто иное, чем Spring Boot CommandLine Runner; извините, никогда не использовал это - person P.J.Meisch; 12.06.2017
comment
Ни один из этих подходов не является даже отдаленно разумным.... Просто переход к 3-х этажному оператору переключения... Ненавижу это говорить, но он сохраняется лучше. Внедрение зависимостей, конечно, 100%, но как только вы выйдете за рамки этого... - person J. M. Becker; 01.10.2018

Странно нет встроенного механизма выбора набора CommandLineRunner. По умолчанию все они выполняются.

Я повторно использовал - возможно, неправильно - механизм профиля, то есть я аннотировал каждый CommandLineRunner с помощью @org.springframework.context.annotation.Profile("mycommand") и выбираю тот, который хочу выполнить, с системным свойством -Dspring .profiles.active=моя команда

Для получения дополнительной информации о механизме профиля см. https://www.baeldung.com/spring-profiles

person Pierluigi Vernetto    schedule 30.03.2019

Аналогично этому ответу https://stackoverflow.com/a/44482525/986160, но с использованием инъекции и меньшей конфигурации.

Имея аналогичное требование, у меня сработало то, что один класс CommandLineApps реализует CommandLineRunner, затем вводит все мои другие бегуны командной строки как @Component и использует первый аргумент для делегирования одному из внедренных бегунов. Обратите внимание, что внедренные не должны расширять CommandLineRunner и не должны быть аннотированы как @SpringBootAppplication.

Важно: будьте осторожны, если вы поместите вызов в crontab, один вызов уничтожит предыдущий, если он не будет выполнен.

Вот пример:

@SpringBootApplication
public class CommandLineApps implements CommandLineRunner {

    @Autowired
    private FetchSmsStatusCmd fetchSmsStatusCmd;

    @Autowired
    private OffersFolderSyncCmd offersFolderSyncCmd;

    public static void main(String[] args) {
        ConfigurableApplicationContext ctx = SpringApplication.run(CommandLineApps.class,  args);
        ctx.close();
    }

    @Override
    public void run(String... args) {

        if (args.length == 0) {
            return;
        }

        List<String> restOfArgs = Arrays.asList(args).subList(1, args.length);

        switch (args[0]) {
            case "fetch-sms-status":
                fetchSmsStatusCmd.run(restOfArgs.toArray(new String[restOfArgs.size()]));
                break;
            case "offers-folder-sync":
                offersFolderSyncCmd.run(restOfArgs.toArray(new String[restOfArgs.size()]));
                break;
        }
    }
}
@Component
public class FetchSmsStatusCmd {

    [...] @Autowired dependencies    

    public void run(String[] args) {

        if (args.length != 1) {
            logger.error("Wrong number of arguments");
            return;
        }
        [...]
    }
 }
person Michail Michailidis    schedule 09.11.2019