Guide To Running Logic on Startup in Spring – 在Spring启动时运行逻辑的指南

最后修改: 2016年 8月 18日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this tutorial, we’ll focus on how to run logic at the startup of a Spring application.

在本教程中,我们将重点讨论如何在Spring应用程序的启动阶段运行逻辑。

2. Running Logic on Startup

2.启动时运行逻辑

Running logic during/after Spring application’s startup is a common scenario. But it’s also one that causes multiple problems.

在Spring应用程序启动期间/之后运行逻辑是一种常见的情况。但这也是一个会导致多种问题的情况。

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.

我们不能简单地将我们的逻辑包含在Bean的构造器中,或在任何对象的实例化后调用方法,因为我们在这些过程中不在控制范围内。

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.

在这里,我们试图在构造函数中访问一个autowired字段。当构造函数被调用时,Spring Bean还没有被完全初始化。这是一个问题,因为调用尚未初始化的字段将导致NullPointerExceptions。

Let’s look at a few ways Spring gives us to manage this situation.

让我们看看Spring给我们提供的管理这种情况的几种方法。

2.1. The @PostConstruct Annotation

2.1.@PostConstruct 注释

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.

我们可以使用Javax的 @PostConstruct注解来注解一个方法,该方法应在Bean初始化后立即运行一次。请记住,即使没有任何东西可以注入,Spring也会运行注解的方法。

Here’s @PostConstruct in action:

这里是@PostConstruct的操作。

@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.

我们可以看到,Environment实例被安全地注入,然后在@PostConstruct注解的方法中被调用,没有抛出NullPointerException

2.2. The InitializingBean Interface

2.2.InitializingBean接口

The InitializingBean approach works in a similar way. Instead of annotating a method, we need to implement the InitializingBean interface and the afterPropertiesSet() method.

InitializingBean方法以类似的方式工作。我们需要实现InitializingBean接口和afterPropertiesSet()方法,而不是注解一个方法。

Here we implement the previous example using the InitializingBean interface:

这里我们使用InitializingBean接口实现前面的例子。

@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

2.3.一个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.

我们可以将这种方法用于在Spring上下文初始化后运行逻辑。因此,我们并没有关注任何特定的Bean。相反,我们正在等待它们全部初始化。

In order to do this, we need to create a bean that implements the ApplicationListener<ContextRefreshedEvent> interface:

为了做到这一点,我们需要创建一个实现ApplicationListener<ContextRefreshedEvent>接口的bean。

@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:

我们可以通过使用新引入的@EventListener注解来获得同样的结果。

@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.

我们要确保为我们的需求挑选一个合适的事件。在这个例子中,我们选择了ContextRefreshedEvent

2.4. The @Bean initMethod Attribute

2.4.@Bean initMethod 属性

We can use the initMethod property to run a method after a bean’s initialization.

我们可以使用 initMethod属性来在Bean初始化后运行一个方法。

Here’s what a bean looks like:

这里是一个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()));
    }
}

Notice we haven’t implemented any special interfaces or used any special annotations.

注意我们没有实现任何特殊的接口,也没有使用任何特殊的注解。

Then we can define the bean using the @Bean annotation:

然后我们可以使用@Bean注解来定义Bean。

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

And this is how a bean definition looks in an XML config:

这就是一个bean定义在XML配置中的样子。

<bean id="initMethodExampleBean"
  class="com.baeldung.startup.InitMethodExampleBean"
  init-method="init">
</bean>

2.5. Constructor Injection

2.5.构造函数注入

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

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.

Spring Boot提供了一个CommandLineRunner接口,其中有一个回调run()方法。这可以在Spring应用上下文实例化后的应用启动时调用。

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.

注意。正如文档中提到的,可以在同一个应用程序上下文中定义多个CommandLineRunner Bean,并且可以使用@Ordered接口或@Order注解来排序。

2.7. Spring Boot ApplicationRunner

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.

CommandLineRunner类似,Spring Boot也提供了一个ApplicationRunner接口,其run()方法将在应用程序启动时被调用。但是,我们没有将原始的String参数传递给回调方法,而是有一个ApplicationArguments类的实例。

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.

ApplicationArguments接口有一些方法来获取作为选项的参数值和普通参数值。一个前缀为- -的参数是一个选项参数。

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

3.结合机制

In order to have full control over our beans, we could combine the above mechanisms together.

为了完全控制我们的Bean,我们可以把上述机制结合在一起。

This is the order of execution:

这就是执行的顺序。

  1. constructor
  2. @PostConstruct annotated methods
  3. InitializingBean’s afterPropertiesSet() method
  4. initialization method specified as init-method in XML

Let’s create a Spring bean that combines all mechanisms:

让我们创建一个结合所有机制的Spring Bean。

@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:

如果我们尝试实例化这个Bean,我们可以看到符合上面指定的顺序的日志。

[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

4.结论

In this article, we showed multiple ways of running logic on Spring’s application startup.

在这篇文章中,我们展示了在Spring的应用启动时运行逻辑的多种方式。

Code samples can be found on GitHub.

代码样本可以在GitHub上找到