I am trying to run a Spring Boot Application with multiple CommandLineRunner implementations in hope, that all run methods will be started.
But it is only one of them, anyway both Implementations are created.
First:
#Component
public class TestRunnerA implements CommandLineRunner {
#Override
public void run(String... args) throws Exception {
logger.info("starting: TestRunnerA");
consume();
}
}
Second:
#Component
public class TestRunnerB implements CommandLineRunner {
#Override
public void run(String... args) throws Exception {
logger.info("starting: TestRunnerB");
consume();
}
}
In this case, only the Run() Method of TestRunnerA ist called.
Does somebody know why?
I tried adding a #Order annotation, but still... (the first in the order is called)
Kind regards,
Knut
You can annotate different runners with different spring profiles and specify required profile in your launching script using:
-Dspring.profiles.active=YourProfile
You could make a single SpringBootApplication that implements CommandLineRunner then inject all the other runners as #Component and use the first argument to delegate which command should take the rest of the arguments. Here is an example in my answer here: https://stackoverflow.com/a/58777948/986160
Important: be careful though if you put it in a crontab one call will destroy the previous one if it is not done.
Related
I'd like to test my Spring Boot command line application. I would like to mock certain beans (which I was able to do by annotating #ContextConfiguration(classes = TestConfig.class) at the top of my test class. In TestConfig.class, I override the beans that I would like to mock. I'd like Spring Boot to find the rest of the components. This seems to work.
The problem is that when I run the test, the entire application starts up as normal (ie. the run() method is called).
#Component
public class MyRunner implements CommandLineRunner {
//fields
#Autowired
public MyRunner(Bean1 bean1, Bean2 bean2) {
// constructor code
}
#Override
public void run(String... args) throws Exception {
// run method implementation
}
I've tried to override the MyRunner #Bean and put it in TestConfig.class, but that doesn't seem to work. I understand that I'm loading the regular application context, but that's what I'd like to do (I think?) since I would like to re-use all (or most) of the #Component I created in my Application, and only mock a tiny subset.
Any suggestions?
EDIT:
Application.java
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
The answer was simpler than I thought. Add the MockBean in
#TestConfiguration
public class TestConfig {
#MockBean
private MyRunner myRunner;
}
We can use the #MockBean to add mock objects to the Spring application context. The mock will replace any existing bean of the same type in the application context.
So MyRunner.run() is never called but I can still use all the other beans in my application.
CommandLineRunners are ordinary beans with one exception:
After the application context is loaded, spring boot finds among all its beans the beans that implement this interface and calls their run method automatically.
Now, I would like you to ask to do the following:
Remove ContextConfiguration from the test and place a breakpoint in constructor of MyRunner. The test should look like this:
#RunWith(SpringRunner.class) // if you're on junit 4, adjust for junit 5 if you need
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
public class MyTest {
#Autowired
private MyRunner myRunner;
#Test
public void testMe() {
System.out.println("hello");
}
}
Run the test and make sure that myRunner is loaded and its run method is called
Now mock this class with MockBean annotation:
#RunWith(SpringRunner.class) // if you're on junit 4, adjust for junit 5 if you need
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
public class MyTest {
#MockBean
private MyRunner myRunner;
#Test
public void testMe() {
System.out.println("hello");
}
}
Run the test. Make sure that run method is not running. Your Application Context now should contain a mock implementation of your component.
If the above works, then the problem is with TestConfig and ContextConfiguration annotation. In general when you run without ContextConfiguration you give spring boot test engine a freedom to mimic the application context started as if its a real application (with autoconfigurations, property resolution, recursive bean scanning and so forth).
However if you put ContextConfiguration, spring boot test doesn't work like this - instead it only loads the beans that you've specified in that configuration. No Autoconfigurations, no recursive bean scanning happens for example.
Update
Based on OP's comment:
It looks like the MyRunner gets loaded when you put #ContextConfiguration because of component scanning. Since you have an annotation #Component placed on MyRunner class it can be discovered by Spring boot engine.
In fact there is a "dangerous" mix of two types of beans definitions here:
1. The beans defined with #Bean in #Configuration annotation
2. The beans found during component scanning.
Here is the question for you: If you don't want to mimic the application startup process and instead prefer to load only specific beans, why do you use #SpringBootTest at all? Maybe you can achieve the goal with:
#RunWith(SpringRunner.class)
#ContextConfiguration(YourConfig.class)
public class MyTest {
...
}
One way you could do this is to have 2 classes with the main method, one which sets up the "normal" context, and another that sets up the "mock" context:
Normal App Context, uses the usual Application
#SpringBootApplication(scanBasePackages = "com.example.demo.api")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
public Foo foo() {
return new Foo("I am not mocked");
}
#Bean
public Bar bar() {
return new Bar("this is never mocked");
}
}
Add another Application class that overrides the normal context with the mocked one
#SpringBootApplication(scanBasePackageClasses = {MockApplication.class, Application.class})
#Component
public class MockApplication {
public static void main(String[] args) {
SpringApplication.run(MockApplication.class, args);
}
#Bean
public Foo foo() {
return new Foo("I am mocked");
}
}
When you run Application.main Foo will be "I am not mocked", when you run MockApplication.main() it will be "I am mocked"
In Sprint Boot 2.x we can initialize our application in one of 2 approaches:
#SpringBootApplication
public class MyApp {
public static void main(String[] args) {
SpringApplication.run(MyApp.class, args);
init();
}
private void init() {
// Init the app from in here...
}
}
Or we can use a startup listener that will execute on startup:
public class StartupListener implements ApplicationListener<ContextRefreshedEvent> {
#Override
public void onApplicationEvent(ContextRefreshedEvent event) {
// Init the app from in here...
}
}
I'm wondering what tradeoffs exist by taking either approach. Not knowing much about Spring Boot's "app lifecycle", I'm wondering if there are things I will/won't have access to in either setup. Thanks in advance!
The init method is only called after startup, and is only called when running your application as a command-line program.
The init method is e.g. not called when deploying your application as a .war file.
The onApplicationEvent method is called whenever a ContextRefreshedEvent is fired, which does happen during startup, but can be called again later. See e.g. "When is ContextRefreshedEvent fired in Spring?"
For a more comparable event to the init method, use ApplicationStartedEvent.
In what circumstances CommandLineRunner is preferred instead of writing additional code in the main method of SpringBoot application.
I understand that CommandLineRunner gets executed before main gets completed.
In simple cases, there is no difference.
But if the code need to access features provided by spring, such as ioc or only interface repositories/services, you need to wait for the complete application startup. And the call of the overrided run method after completion is garanteed.
Besides, CommandLineRunner has other advantages:
Can be implemented multiple times
The capability to start any scheduler or log any message before application starts to run
I have used it to decouple code. Instead of placing a bunch of code into main method, the CommandLineRunner lets you distribute it more evenly around the codebase. It really depends on what kind of flags you are passing in and why you need to pass them in. Spring offers a lot of flexibility for you to get the job done in the easiest way.
For a full on command line tool, you can decouple initialization and config a little bit by dividing your code between init and core behavior.
A spring boot server can overwrite configuration based on args passed in from the command line.
I would suggest all time time. It adds a lot of flexibility to your "bootstrapping code".
1) For example, command line runners are spring #Beans, so you can activate/deactivate them at run-time.
2) You can use them in an ordered fashion by appending the #Order annotation
3) You can unit test them just like regular classes
4) You can inject dependencies into them. Each runner can then have its own dependencies.
All of the above are more difficult, if not impossible to achieve if you add all your bootstrapping logic in the main() method of the Spring Application class.
Hope my answer helps,
Cheers
I haven't found any good reason for using it over just writing code after starting the application.
The only thing I can think of is that the command line runners are called before any SpringApplicationRunListeners have #finished called.
I have seen people use them to perform main application logic, but I think this is an antipattern.
One of the annoying things about doing so is that the application start timer is still running and when the task completes you will see a log entry like Started DemoApplication in 5.626 seconds (JVM running for 0.968).
It's confusing to see a message about your application starting despite, in reality it having just finished.
I encountered a scenario where i had to keep a certain data from the db loaded into the cache before the method was hit from the controller end point the first time. In this scenario it was desirable to hit the method for populating the cache using the run method after extending CommandLineRunner class so that before the application even starts up the data is already available in the cache.
I use this to populate my Default Data. I usually create ApplicationInitializer class that extends CommandLineRunner.
I have methods like createDefaultUser(), createDefaultSytemData() etc.
This way I do not rely on sql files to populate database for me. :)
ApplicationRunner and CommandLineRunner:
two of them can execute some custom code before your application finished starting up.
ComandLineRunner:
#Component
public class CommandLineAppStartupRunner implements CommandLineRunner {
private static final Logger logger = LoggerFactory.getLogger(CommandLineAppStartupRunner.class);
#Override
public void run(String...args) throws Exception {
logger.info(Arrays.toString(args));
}
}
you can get the args directly
ApplicationRunner:
#Component
public class AppStartupRunner implements ApplicationRunner {
private static final Logger logger = LoggerFactory.getLogger(AppStartupRunner.class);
#Override
public void run(ApplicationArguments args) throws Exception {
logger.info(args.getOptionNames());
}
}
ApplicationRunner has many methods to get params
ComandLineRunner can get params directly
if you custom two runner class, you can use annotation #Order to Specify the order of execution
public class Phone {
#Autowired
BeanExample beanExample;
public void print(){
beanExample.fn();
}
}
public class BeansCreatorClass {
#Bean
public BeanExample getBeanExample(){
return new BeanExample();
}
#Bean
public Phone getPhone(){
return new Phone();
}
}
#SpringBootApplication
public class SpringBootRunnerConfigurationPropertiesApplication implements CommandLineRunner, ApplicationRunner {
public static void main(String[] args){
SpringApplication.run(SpringBootRunnerConfigurationPropertiesApplication.class, args);
System.out.println("==== spring boot commandLine is running === ");
// beans creator class is the class contains all beans needed
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeansCreatorClass.class);
Phone phone = applicationContext.getBean(Phone.class);
phone.print();
}
// commandLineRunner
public void run(String... args) throws Exception {
System.out.println("=== commandLine Runner is here ==== ");
}
// application runner
#Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("=== application runner is here ====");
}
}
I mostly use CommandLineRunner to:
Apply initial migrations
Run a code that is independent of REST/SOAP calls.
I would like to be able to use a bean via auto-wiring and without having to directly use an ApplicationContext. Below is a dummy example of what I would like to be able to do.
Configuration Class
#Configuration
public class CoffeeConfig
{
#Bean
public CoffeeMachine provideCoffeeMachine()
{
return new CoffeeMachine(provideCoffeeBean());
}
#Bean
public CoffeeBean provideCoffeeBean()
{
return new CoffeeBean(Type.BEST);
}
}
Coffee Shop Class
#Component
public class CoffeeShop
{
#Autowired
private CoffeeMachine cMachine;
public void pourCoffee()
{
System.out.print("Pouring cup of coffee: " + cMachine.pour(Amount.8OZ));
}
}
In order to solve this, I have been reading through spring documentation and spring tutorials. The problem is, I haven't seen anyone attempt to illustrate how to do something as simple as this, and when they do, they end up resorting to using an application context. That being said, I know that if I am running unit tests with Spring, I can do the following:
Test Class
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes=CoffeeConfig.class, loader=AnnotationConfigContextLoader.class)
public class SpringIOCTests
{
#Autowired
public CoffeeMachine cMachine;
#Test
public void influxDevTest()
{
assertEquals(Type.BEST, cMachine.getBeans());
}
}
The way this configures leads me to believe that using auto-wiring in such a way should be attainable in the actual application instead of using these test-only dependencies such as the ContextConfiguration. I should also note that this unit test does pass.
Does Spring offer a methodology in which one can auto-wire dependencies in a nice and clean way avoiding the direct use of an application contexts?
I need to run a method after the Spring Application Context of my web app has started up. I looked at this question but it refers to Java Servlet startup, and none of the Spring stuff has run at that point.
Is there a "SpringContext.onStartup()" method I can hook into?
Use something like the following code:
#Component
public class StartupListener implements ApplicationListener<ContextRefreshedEvent> {
#Override
public void onApplicationEvent(final ContextRefreshedEvent event) {
// do your stuff here
}
}
Of course StartupListener will need to be within the component scan's reach
Take note however that if your application uses multiple contexts (for example a root context and a web context) this method will be run once for each context.
You can write listener like this:
#Component
public class SpringContextListener implements ApplicationListener<ApplicationEvent> {
public void onApplicationEvent(ApplicationEvent arg0) {
System.out.println("ApplicationListener");
};
}
Just add component scan path like this:
<context:component-scan base-package="com.controller" />
Have a look at Better application events in Spring Framework 4.2
#Component
public class MyListener {
#EventListener
public void handleContextRefresh(ContextRefreshedEvent event) {
...
}
}
Annotate a method of a managed-bean with #EventListener to automatically register an ApplicationListener matching the signature of the method. #EventListener is a core annotation that is handled transparently in a similar fashion as #Autowired and others: no extra configuration is necessary with java config and the existing < context:annotation-driven/> element enables full support for it.