We often need to do some hook actions when the container starts, such as registering message consumers, listening to configurations, etc. Today we will summarize the 7 startup extensions that SpringBoot leaves to developers.

Container refresh completion extension point

1.ApplicationListener<ContextRefreshedEvent>

Anyone familiar with Spring must know that a successful container refresh means that all Bean initialization has been completed, and when the container is refreshed Spring will call the onApplicationEvent method of all Beans that implement ApplicationListener<ContextRefreshedEvent> in the container. onApplicationEvent` method, which the application can use to listen for container initialization completion events.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@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++;
    }
}

This extension point needs extra attention when used in the web container. In web projects (such as spring mvc), there are two containers, one is the root application context and the other is our own context (as a sub-container of the root application context as a sub-container of root application context). If we write it in this way, the onApplicationEvent method will be executed twice. The solution to this problem is as follows.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
@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) {
        if (event.getApplicationContext().getParent() == null) {
            // root application context 没有parent
            LOG.info("Increment counter");
            counter++;
        }
    }
}

Advanced usage

Of course there is a more advanced use of this extension: Custom Events, which can be implemented as an observer pattern at minimal cost with the help of Spring.

  • First customize an event.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    
    public class NotifyEvent extends ApplicationEvent {
        private String email;
        private String content;
        public NotifyEvent(Object source) {
            super(source);
        }
        public NotifyEvent(Object source, String email, String content) {
            super(source);
            this.email = email;
            this.content = content;
        }
        // 省略getter/setter方法
    }
    
  • Register an event listener

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    @Component
    public class NotifyListener implements ApplicationListener<NotifyEvent> {
    
        @Override
        public void onApplicationEvent(NotifyEvent event) {
            System.out.println("邮件地址:" + event.getEmail());
            System.out.println("邮件内容:" + event.getContent());
        }
    }
    
  • publish event

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    
    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class ListenerTest {
        @Autowired
        private WebApplicationContext webApplicationContext;
    
        @Test
        public void testListener() {
            NotifyEvent event = new NotifyEvent("object", "abc@qq.com", "This is the content");
            webApplicationContext.publishEvent(event);
        }
    }
    
  • Executing the unit test you can see that the address and the content of the email are printed out.

2. CommandLineRunner interface

When the container context is initialized, SpringBoot will also call all run methods that implement the CommandLineRunner interface, and the following code will do the same thing as above.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
@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++;
    }
}

There are two additional points to note about the use of this extension point.

  • The execution order of multiple Beans that implement CommandLineRunner can be adjusted based on the @Order annotation on the Bean.

  • The run method can accept parameters from the console, which is more flexible than the ApplicationListener<ContextRefreshedEvent> extension.

    1
    2
    
    // 从控制台输入参数示例
    java -jar CommandLineAppStartupRunner.jar abc abcd
    

3. ApplicationRunner interface

This extension is similar to the CommandLineRunner interface extension of SpringBoot, except that the accepted argument is an ApplicationArguments class that provides better encapsulation of the console input arguments, starting with -- is considered an argument with options, otherwise it is a normal argument.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
@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++;
    }
}

For example.

1
java -jar CommandLineAppStartupRunner.jar abc abcd --autho=mark verbose

Bean initialization completion extension point

The previous section summarizes the extension points for container initialization. In some scenarios, such as listening to messages, we want to register listeners immediately after Bean initialization, rather than waiting until the entire container is refreshed, and Spring leaves enough extension points for this scenario as well.

1. @PostConstruct annotation

The @PostConstruct annotation is placed on the methods of a Bean, and the methods modified by @PostConstruct are called immediately after the initialization of the Bean.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@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()));
    }
}

2. InitializingBean interface

The usage of InitializingBean is basically the same as @PostConstruct, except that the corresponding Bean needs to implement the afterPropertiesSet method.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@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()));
    }
}

3. Initialization methods for @Bean annotations

The initialization method can be specified when injecting a Bean with @Bean.

Definition of Bean

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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()));
    }
}

Bean injection

1
2
3
4
@Bean(initMethod="init")
public InitMethodExampleBean initMethodExampleBean() {
    return new InitMethodExampleBean();
}

4. Injection via constructor

Spring also supports injection via constructor, we can write the code in the constructor and achieve the same purpose.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@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()));
    }
}

Bean initialization completion extension point execution order?

A simple test can be used to.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@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");
    }
}

Output after instantiating this Bean.

1
2
3
4
[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

Reference http://www.enmalvi.com/2022/05/21/springboot-start/