Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multiple Spring boot CommandLineRunner based on command line argument

I have created spring boot application with spring cloud task which should executes a few commands(tasks). Each task/command is shorted-lived task, and all tasks are start from command line, do some short ETL job and finish execution.

There is one spring boot jar which contain all the commands/tasks. Each task is CommandLineRunner, and I like to decide which tasks (one or more) will be executed based on the params from command line. What is the best practice to do so? I don't like to have dirty code which ask "if else" or something like this.

like image 722
Shay Avatar asked Jun 11 '17 07:06

Shay


2 Answers

You can also make your CommandLineRunner implementations @Component and @ConditionalOnExpression("${someproperty:false}")

then have multiple profiles, that set someproperty to true to include those CommandLineRunners in the Context.

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

and in the yml application-myrunner.yml

myRunnerEnabled: true
@SpringBootApplication
public class SpringMain {
    public static void main(String ... args) {
        SpringApplication.run(SpringMain.class, args);
    }
}
like image 196
user2043566 Avatar answered Sep 23 '22 13:09

user2043566


Spring Boot runs all the CommandLineRunner or ApplicationRunner beans from the application context. You cannot select one by any args.

So basically you have two possibiities:

  1. You have different CommandLineRunner implementations and in each you check the arguments to determine if this special CommandLineRunner should run.
  2. You implement only one CommandLineRunner which acts as a dispatcher. Code might look something like this:

This is the new Interface that your runners will implement:

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

You then define implementations and identify them with a name:

@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");
    }
}

and

@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");
    }
}

Then in your single CommandLineRunner implementation you get hold of the application context and resolve the required bean by name, my example uses just the first argument, and call it's 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;
    }
}
like image 43
P.J.Meisch Avatar answered Sep 20 '22 13:09

P.J.Meisch