我已经使用 spring cloud 任务创建了 spring boot 应用程序,它应该执行一些命令(任务)。每个任务/命令都是短期任务,所有任务都是从命令行开始,做一些简短的 ETL 工作并完成执行。
有一个包含所有命令/任务的 spring boot jar。每个任务都是 CommandLineRunner,我喜欢根据命令行的参数来决定将执行哪些任务(一个或多个)。这样做的最佳做法是什么?我不喜欢有脏代码问“if else”或类似的东西。
我已经使用 spring cloud 任务创建了 spring boot 应用程序,它应该执行一些命令(任务)。每个任务/命令都是短期任务,所有任务都是从命令行开始,做一些简短的 ETL 工作并完成执行。
有一个包含所有命令/任务的 spring boot jar。每个任务都是 CommandLineRunner,我喜欢根据命令行的参数来决定将执行哪些任务(一个或多个)。这样做的最佳做法是什么?我不喜欢有脏代码问“if else”或类似的东西。
您还可以使您的 CommandLineRunner 实现 @Component 和 @ConditionalOnExpression("${someproperty:false}")
然后有多个配置文件,将 someproperty 设置为 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 application-myrunner.yml
myRunnerEnabled: true
@SpringBootApplication
public class SpringMain {
public static void main(String ... args) {
SpringApplication.run(SpringMain.class, args);
}
}
Spring Boot从应用程序上下文运行所有CommandLineRunner
或bean。ApplicationRunner
您不能通过任何参数选择一个。
所以基本上你有两种可能性:
CommandLineRunner
实现,并在每个实现中检查参数以确定是否CommandLineRunner
应该运行此特殊功能。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()
方法:
@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;
}
}
奇怪的是没有内置机制来选择一组 CommandLineRunner。默认情况下,它们都被执行。
我已经重用了——也许是不正确的——Profile 机制,也就是说我用@org.springframework.context.annotation.Profile("mycommand") 注释了每个CommandLineRunner,然后我选择了我想用系统属性-Dspring 执行的那个.profiles.active=我的命令
Profile机制的更多信息,请参考https://www.baeldung.com/spring-profiles
类似于这个答案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;
}
[...]
}
}