7 Initializations for SpringBoot

Keywords: Java Spring SpringBoot xml

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:

  1. Constructor Method
  2. @PostConstruct annotation method
  3. afterPropertiesSet() of InitializingBean
  4. Bean-defined initMethod attribute method

Reference Documents

https://www.baeldung.com/running-setup-logic-on-startup-in-spring

Beauty Codist's SpringBoot tutorial series

Posted by FaT3oYCG on Tue, 02 Jul 2019 09:13:12 -0700