In Spring Boot, the
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.
@SpringBootApplication will scan components (or beans) in current and descendant packages. When multiple
CommandLineRunners are discovered, Spring will execute them all. So the first approach will be separating those runners into different packages.
If there is a
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:
If there are multiple classes or packages that you want to import, you may as well change the base packages property:
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:
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 (
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.
To run this example in IDEA, use the following configuration:
-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.
@Conditional, Spring Profiles can also be used to filter beans.
You can either activate the profile in command line arguments or environment variables.
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.
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.
Example code can be found on GitHub.