background
In daily development, we often need to execute a piece of logic when the SpringBoot application starts, as in the following scenarios:
- Get some configuration or variables for the current environment
- Write some initial data to the database
- Connect some third-party systems to confirm that the other party can work..
When implementing these functions, we may encounter some pits.To take advantage of the convenience of the SpringBoot framework, we had to give the execution control of the entire application to the container, which made the details unknown.
Then you need to be careful when implementing the initialization logic code. For example, we can't simply implement the initialization logic in the construction method of the Bean class, like the following code:
@Component public class InvalidInitExampleBean { @Autowired private Environment env; public InvalidInitExampleBean() { env.getActiveProfiles(); } }
Here, we try to access an auto-injected env field in the construction method of InvalidInitExampleBean, and when it does, you will get a null pointer exception (NullPointerException).
The reason is that when a construction method is called, the Environment Bean in the Spring context is probably not instantiated yet and is not injected into the current object, so it cannot be called like this.
Next, let's look at some of the ways you can implement "secure initialization" in SpringBoot:
1. @PostConstruct comment
The @PostConstruct annotation actually comes from the javax extension package (most people think of it as coming from the Spring framework) and is used to declare the method that will be executed when a Bean object is initialized.
Take a look at its original definition:
The PostConstruct annotation is used on a method that needs to be executed after dependency injection is done to perform any initialization.
That is, the method will not execute until all dependent field injections are made, although this action is also performed by the Spring framework.
The following code demonstrates an example of using @PostConstruct:
@Component public class PostConstructExampleBean { private static final Logger LOG = Logger.getLogger(PostConstructExampleBean.class); @Autowired private Environment environment; @PostConstruct public void init() { //environment has been injected LOG.info(Arrays.asList(environment.getDefaultProfiles())); } }
2. InitializingBean interface
InitializingBean is an interface provided by the Spring framework and works much like the @PostConstruct annotation.
If you do not use annotations, you need to have the Bean instance inherit the InitializingBean interface and implement the afterPropertiesSet() method.
The following code shows this usage:
@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 { //environment has been injected LOG.info(Arrays.asList(environment.getDefaultProfiles())); } }
3. @Bean initMethod method
When declaring a Bean, you can also specify an initMethod property that points to a method of the Bean that executes after initialization.
As follows:
@Bean(initMethod="init") public InitMethodExampleBean exBean() { return new InitMethodExampleBean(); }
Then, here we point the initMethod to the init method, which we also need to implement in Bean:
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())); } }
The code above is based on Java annotations, and the same can be achieved with Xml configuration:
<bean id="initMethodExampleBean" class="org.baeldung.startup.InitMethodExampleBean" init-method="init"> </bean>
This approach was heavily used in earlier versions of Spring
4. Constructor Injection
If the dependent fields are declared in the Bean's construction method, the Spring framework first instances the Bean corresponding to these fields, then invokes the current construction method.
At this point, some operations in the construction method are also safe, as follows:
@Component public class LogicInConstructorExampleBean { private static final Logger LOG = Logger.getLogger(LogicInConstructorExampleBean.class); private final Environment environment; @Autowired public LogicInConstructorExampleBean(Environment environment) { //environment instance initialized this.environment = environment; LOG.info(Arrays.asList(environment.getDefaultProfiles())); } }
5, ApplicationListener
The ApplicationListener is an interface provided by the spring-context component that is primarily used to listen for "life cycle events in container context".
It is defined as follows:
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener { /** * Handle an application event. * @param event the event to respond to */ void onApplicationEvent(E event); }
Events here can be any event object that inherits from the ApplicationEvent.For initialization, we can capture the timing of context initialization by listening for the ContextRefreshedEvent event.
As follows:
@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++; } }
Once the Spring context initialization is complete, the method defined here will be executed.
Unlike the previous Initializing Bean s, the way you listen through the ApplicationListener is global, meaning that the method is not executed until all beans have been initialized.
A new @EventListener annotation was introduced after Spring 4.2 to achieve the same effect:
@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++; } }
6, CommandLineRunner
SpringBoot provides a CommanLineRunner interface for logical control after application startup, which is defined as follows:
public interface CommandLineRunner { /** * Callback used to run the bean. * @param args incoming main method arguments * @throws Exception on error */ void run(String... args) throws Exception; }
The run method here executes after the Spring context initialization is complete and passes in the application's startup parameters.
As follows:
@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 { //Context Initialization Completed LOG.info("Increment counter"); counter++; } }
Additionally, in the case of multiple CommandLineRunner s, you can use the @Order annotation to control their order.
7, ApplicationRunner
Similar to the CommandLineRunner interface, Spring boot also provides another Application Runner interface to implement initialization logic.
The difference is that the ApplicationRunner.run() method accepts encapsulated ApplicationArguments parameter objects instead of simple string parameters.
@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++; } }
The ApplicationArguments object provides some very convenient methods for getting parsed parameters directly, such as:
java -jar application.jar --debug --ip=xxxx
The getOptionNames from ApplicationArguments gives you a value like ['debug','ip']
Test Code
Below, a small test demonstrates the execution order of several initialization methods to implement a composite Bean in the following code:
@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"); } //Define as initMethod in XML public void init() { LOG.info("init-method"); } }
Performing the initialization of this Bean, you will find that the log output is as follows:
[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
So the order of these initializations is:
- Constructor Method
- @PostConstruct annotation method
- afterPropertiesSet() of InitializingBean
- Bean-defined initMethod attribute method
Reference Documents
https://www.baeldung.com/running-setup-logic-on-startup-in-spring