使用Spring啟動時運行自定義業務

在Spring應用啟動時運行自定義業務的場景很常見,但應用不當也可能會導致一些問題。

基於Spring控制反轉(Inverse of Control)功能用戶幾乎不用幹預bean實例化過程,對於自定義業務則需要控制部分流程及容器,因此值得須特別關註。

1. Spring啟動時運行自定義業務

我們不能簡單包括自定義業務在bean的構造函數或在實例化任何對象之後調用方法,這些過程不由我們控制。請看示例:

@Component
public class InvalidInitExampleBean {
    @Autowired
    private Environment env;
    public InvalidInitExampleBean() {
        env.getActiveProfiles();
    }
}

這裡嘗試在構造函數中訪問自動裝配的屬性。當調用構造函數時,Spring bean仍沒有全部初始化,因此導致NullPointerExceptions異常。下面介紹幾種方式解決此問題。

1.1 @PostConstruct 註解

@PostConstruct註解用於方法上,實現bean初始化後立刻執行一次。需要註意的是,即使沒有對象註入,Spring也會執行註解方法。

@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()));
    }
}

上面示例可以實現Environment environment被安全註入,然後調用註解方法且不會出現空指針異常。

1.2 InitializingBean 接口

InitializingBean接口實現功能與上節類似。但需要實現接口並重寫afterPropertiesSet方法。

下面重寫前節的示例:

@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()));
    }
}

1.3 ApplicationListener 監聽器

該方法可用於在Spring上下文初始化之後執行自定義業務。因此不針對特定bean,而是等待所有bean初始化之後。應用時需要實現ApplicationListener接口:

@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++;
    }
}

同樣可以引入@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++;
    }
}

上面示例使用ContextRefreshedEvent,具體選擇哪種事件根據你的業務需要。

1.4 @Bean的初始化方法

該註解的initMethod屬性可用於在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()));
    }
}

既不要實現接口,也不要特定註解。通過註解定義Bean:

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

對應xml配置:

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

1.5 構造函數註入

如果使用構造器註入屬性,可以簡單地在構造函數中包括業務:

@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()));
    }
}

1.6 Spring Boot CommandLineRunner接口

Spring Boot 提供瞭CommandLineRunner接口,重寫run方法,可以在應用啟動時Spring應用上下文實例化之後調用。

@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++;
    }
}

CommandLineRunner bean在相同上下文中可以定義多個,通過使用Ordered 接口或@Ordere註解確定順序。

1.7 Spring Boot ApplicationRunner

與CommandLineRunner類似,Spring Boot 也提供瞭ApplicationRunner接口,重寫run方法可以實現應用啟動時執行自定義業務。另外其回調方法沒有使用String參數,而是使用ApplicationArguments類的實例。

ApplicationArguments有方法可以獲取可選參數及普通參數的值,參數前有–的表示可選參數。

@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++;
    }
}

2. 執行順序

多種方法對bean同時進行控制,對應執行順序如下:

  1. 構造函數
  2. @PostConstruct註解方法
  3. InitializingBean的afterPropertiesSet()
  4. @Bean或xml中標註的初始化方法

讀者可以自行測試進行驗證。

3. 總結

本文介紹多種方式實現在Spring啟動時實現自定義業務。通過對比不同方式實現加深對Spring的理解,掌握更多控制bean實例化過程的方式。以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。

推薦閱讀: