In Spring Boot, the CommandLineRunner
and ApplicationRunner
are two utility interfaces that we can use to execute code when application is started. However, all beans that implement these interfaces will be invoked by Spring Boot, and it takes some effort to execute only a portion of them. This is especially important when you are developing a console application with multiple entry points. In this article, we will use several techniques to achieve this goal.
Put CommandLineRunner in different packages
By default, @SpringBootApplication
will scan components (or beans) in current and descendant packages. When multiple CommandLineRunner
s are discovered, Spring will execute them all. So the first approach will be separating those runners into different packages.
1 | package com.shzhangji.package_a; |
If there is a JobB
in package_b
, these two jobs will not affect each other. But one problem is, when executing JobA
, only components defined under package_a
will be scanned. So if JobA
wants to use a service in com.shzhangji.common
package, we have to import this class explicitly:
1 | package com.shzhangji.package_a; |
If there are multiple classes or packages that you want to import, you may as well change the base packages property:
1 |
|
Conditional scanning of components
So the basic idea is to expose only one CommandLineRunner
to Spring’s component scanning mechanism. Luckily Spring Framework provides the @Conditional
annotation that can be used to filter beans based on system property, profile, or more complex conditions. As a matter of fact, Spring Boot’s auto configuration feature is largely based on @Conditional
. For instance:
1 |
|
When initializing the embedded web server, Spring will check if Tomcat or Jetty is on the classpath (@ConditionalOnClass
), and create the corresponding beans. The configuration class itself is also conditionally processed in a web environment (@ConditionalOnWebApplication
).
In our situation, we shall use the @ConditionalOnProperty
annotation, that filters beans based on system properties. Say we accept a property named job
, and only create the CommandLineRunner
bean when their values match.
1 |
|
To run this example in IDEA, use the following configuration:
The -Djob
in VM options and --job
in program arguments are equivalent, so you only need to specify once. This property can also be set in a configuration file.
Similar to @Conditional
, Spring Profiles can also be used to filter beans.
1 |
|
You can either activate the profile in command line arguments or environment variables.
Write a JobDispatcher
Lastly, we can always add a middle layer to solve the problem, i.e. a JobDispatcher
that decides which Runnable
to run. Only this time, we use the ApplicationRunner
instead, because it will help us parsing the command line arguments.
1 |
|
When we pass --job=JobDispatcherA
on the command line, the dispatcher will try to locate the job class, and initialize it with beans defined in context.
1 |
|
Example code can be found on GitHub.