1. Overview
In this tutorial, we'll focus on how to run logic at the startup of a Spring application.
Some of the more useful configs for a Spring Boot application.
Learn how to configure your Spring Boot application's main class in Maven and Gradle.
2. Running Logic on Startup
Running logic during/after Spring application's startup is a common scenario. But it's also one that causes multiple problems.
In order to benefit from Inverse of Control, we need to renounce partial control over the application's flow to the container. This is why instantiation, setup logic on startup, etc. need special attention.
We can't simply include our logic in the beans' constructors or call methods after instantiation of any object because we aren't in control during those processes.
Let's look at a real-life example:
@Component
public class InvalidInitExampleBean {
@Autowired
private Environment env;
public InvalidInitExampleBean() {
env.getActiveProfiles();
}
}
Here we're trying to access an autowired field in the constructor. When the constructor is called, the Spring bean is not yet fully initialized. This is a problem because calling fields that are not yet initialized will result in NullPointerExceptions.
Let's look at a few ways Spring gives us to manage this situation.
2.1. The @PostConstruct Annotation
We can use Javax's @PostConstruct annotation for annotating a method that should be run once immediately after the bean's initialization. Keep in mind that Spring will run the annotated method even if there is nothing to inject.
Here's @PostConstruct in action:
@Component
public class PostConstructExampleBean {
private static final Logger LOG
= Logger.getLogger(PostConstructExampleBean.class);
@Autowired
private Environment environment;
@PostConstruct
public void init() {
LOG.info(Arrays.asList(environment.getDefaultProfiles()));
}
}
We can see that the Environment instance was safely injected and then called in the @PostConstruct annotated method without throwing a NullPointerException.
2.2. The InitializingBean Interface
The InitializingBean approach works in a similar way. Instead of annotating a method, we need to implement the InitializingBean interface and the afterPropertiesSet() method.
Here we implement the previous example using the InitializingBean interface:
@Component
public class InitializingBeanExampleBean implements InitializingBean {
private static final Logger LOG
= Logger.getLogger(InitializingBeanExampleBean.class);
@Autowired
private Environment environment;
@Override
public void afterPropertiesSet() throws Exception {
LOG.info(Arrays.asList(environment.getDefaultProfiles()));
}
}
2.3. An ApplicationListener
We can use this approach for running logic after the Spring context has been initialized. So, we aren't focusing on any particular bean. We're instead waiting for all of them to initialize.
In order to do this, we need to create a bean that implements the ApplicationListener<ContextRefreshedEvent> interface:
@Component
public class StartupApplicationListenerExample implements
ApplicationListener<ContextRefreshedEvent> {
private static final Logger LOG
= Logger.getLogger(StartupApplicationListenerExample.class);
public static int counter;
@Override public void onApplicationEvent(ContextRefreshedEvent event) {
LOG.info("Increment counter");
counter++;
}
}
We can get the same results by using the newly introduced @EventListener annotation:
@Component
public class EventListenerExampleBean {
private static final Logger LOG
= Logger.getLogger(EventListenerExampleBean.class);
public static int counter;
@EventListener
public void onApplicationEvent(ContextRefreshedEvent event) {
LOG.info("Increment counter");
counter++;
}
}
We want to make sure to pick an appropriate event for our needs. In this example, we chose the ContextRefreshedEvent.
2.4. The @Bean initMethod Attribute
We can use the initMethod property to run a method after a bean's initialization.
Here's what a bean looks like:
public class InitMethodExampleBean {
private static final Logger LOG = Logger.getLogger(InitMethodExampleBean.class);
@Autowired
private Environment environment;
public void init() {
LOG.info(Arrays.asList(environment.getDefaultProfiles()));
}
}
Notice we haven't implemented any special interfaces or used any special annotations.
Then we can define the bean using the @Bean annotation:
@Bean(initMethod="init")
public InitMethodExampleBean initMethodExampleBean() {
return new InitMethodExampleBean();
}
And this is how a bean definition looks in an XML config:
<bean id="initMethodExampleBean"
class="com.baeldung.startup.InitMethodExampleBean"
init-method="init">
</bean>
2.5. Constructor Injection
If we're injecting fields using Constructor Injection, we can simply include our logic in a constructor:
@Component
public class LogicInConstructorExampleBean {
private static final Logger LOG
= Logger.getLogger(LogicInConstructorExampleBean.class);
private final Environment environment;
@Autowired
public LogicInConstructorExampleBean(Environment environment) {
this.environment = environment;
LOG.info(Arrays.asList(environment.getDefaultProfiles()));
}
}
2.6. Spring Boot CommandLineRunner
Spring Boot provides a CommandLineRunner interface with a callback run() method. This can be invoked at application startup after the Spring application context is instantiated.
Let's look at an example:
@Component
public class CommandLineAppStartupRunner implements CommandLineRunner {
private static final Logger LOG =
LoggerFactory.getLogger(CommandLineAppStartupRunner.class);
public static int counter;
@Override
public void run(String...args) throws Exception {
LOG.info("Increment counter");
counter++;
}
}
Note: As mentioned in the documentation, multiple CommandLineRunner beans can be defined within the same application context and can be ordered using the @Ordered interface or @Order annotation.
2.7. Spring Boot ApplicationRunner
Similar to CommandLineRunner, Spring Boot also provides an ApplicationRunner interface with a run() method to be invoked at application startup. However, instead of raw String arguments passed to the callback method, we have an instance of the ApplicationArguments class.
The ApplicationArguments interface has methods to get argument values that are options and plain argument values. An argument that is prefixed with – – is an option argument.
Let's look at an example:
@Component
public class AppStartupRunner implements ApplicationRunner {
private static final Logger LOG =
LoggerFactory.getLogger(AppStartupRunner.class);
public static int counter;
@Override
public void run(ApplicationArguments args) throws Exception {
LOG.info("Application started with option names : {}",
args.getOptionNames());
LOG.info("Increment counter");
counter++;
}
}
3. Combining Mechanisms
In order to have full control over our beans, we could combine the above mechanisms together.
This is the order of execution:
- constructor
- @PostConstruct annotated methods
- InitializingBean's afterPropertiesSet() method
- initialization method specified as init-method in XML
Let's create a Spring bean that combines all mechanisms:
@Component
@Scope(value = "prototype")
public class AllStrategiesExampleBean implements InitializingBean {
private static final Logger LOG
= Logger.getLogger(AllStrategiesExampleBean.class);
public AllStrategiesExampleBean() {
LOG.info("Constructor");
}
@Override
public void afterPropertiesSet() throws Exception {
LOG.info("InitializingBean");
}
@PostConstruct
public void postConstruct() {
LOG.info("PostConstruct");
}
public void init() {
LOG.info("init-method");
}
}
If we try to instantiate this bean, we can see logs that match the order specified above:
[main] INFO o.b.startup.AllStrategiesExampleBean - Constructor
[main] INFO o.b.startup.AllStrategiesExampleBean - PostConstruct
[main] INFO o.b.startup.AllStrategiesExampleBean - InitializingBean
[main] INFO o.b.startup.AllStrategiesExampleBean - init-method
4. Conclusion
In this article, we showed multiple ways of running logic on Spring's application startup.
Code samples can be found on GitHub.
Course – LSD (cat=Persistence) res – Persistence (eBook) (cat=Persistence)