Spring IOC容器初始化后,執行一些邏輯操作的幾種實現方式


寫在開篇
在項目的開發中,通常都會用到 Spring 來進行項目管理。在某些應用中,我們希望當Spring 容器將所有的 Bean 都初始化完成后,做一個操作(例如:將數據庫中的字典,加載到內存中)。那么如何在 Spring IOC 容器初始化完成后,自動觸發某個方法來完成某些業務邏輯配置相關的操作執行呢?一共能找到的介紹有如下7種,但是這7種還各自都有些不同之處。
解析8種實現方式

  1. 類實現ApplicationContextAware,重寫setApplicationContext()方法
  2. 類實現InitializingBean,重寫afterPropertiesSet()方法
  3. 在類中的方法上,添加@PostConstruct注解。(@PreDestroy注銷時使用)
  4. 類實現BeanPostProcessor,重寫postProcessBeforeInitialization()、postProcessAfterInitialization()方法
  5. 類實現 SmartLifecycle,重寫相關方法
  6. 類實現ApplicationContextListener,重寫onApplicationEvent()方法
  7. 類實現ApplicationRunner,重寫run()方法
  8. 類實現CommandLineRunner,重寫run()方法

說明:
第 1 、2 種方式,是在所有 bean 對象注冊到 IOC 容器完成之后,通過 后置處理器 或者Spring 啟動后的其他后置操作,來針對指定實現 ApplicationContextAware 或 InitializingBean 的一個bean類進行操作,達到配置全局的目的。比如說:將數據庫中的字典,加載到內存中 這種,並不需要每一個bean都配置的功能。(提示:說到底第1、2種方式,和本文提及的 Spring IOC 初始化后。還是有一些出入的,本文也就一並都放到這里介紹了)
第 3 種方式,PostConstruct和 Spring 的啟動流程無關,只和 Servlet 容器相關。(提示:Spring 項目在啟動 Servlet(此處以 Tomcat 為例)時,已經完成了 IOC 的相關操作,滿足本文題目在 Spring IOC 初始化后)
第 4 種方式,BeanPostProcessor是對 Spring IOC 容器中每個bean對象的前置、后置增強。(提示:也和本文要求 Spring IOC 容器初始化后有出入。本文也就一並都放到這里介紹了))
第 5 種方式,根據源碼我們了解到 LifecycleProcessor 處理器是在Spring 容器啟動最后一步 Last step: finishRefresh();這個方法中調用到的(提示:此時 Spring IOC 容器的初始化工作已經完成滿足本文題目在 Spring IOC 初始化后)
第 6 種方式,使用 ApplicationListener 監聽機制。監聽 ApplicationContextListener<ContextRefreshedEvent>的方式,Spring IOC 容器在所有的bean都初始化完成並被成功裝載后會觸發該事件(提示:監聽器方式 也可以滿足本文題目在 Spring IOC 初始化后,執行部分邏輯操作)
第 7、8 種方式,實現 ApplicationRunner、CommandLineRunner 接口的方式。都是在容器啟動完成的時執行。(提示:這種方式也可以滿足本文題目在 Spring IOC 初始化后,執行部分邏輯操作)
綜合來說: 只有3、5、6、7、8 這五種方式,是徹底在 Spring IOC容器初始化后,執行一些邏輯操作。其它三種方式只能說是變相滿足本文要求吧

使用介紹
1.實現ApplicationContextAware,重寫setApplicationContext()方法
實現ApplicationContextAware接口並重寫setApplicationContext()方法獲取ApplicationContext實例,這個要追溯到ApplicationContextAwareProcessor類。首先我們來看一下 ApplicationContextAwareProcessor 這個類,它是BeanPostProcessor(后置處理器)的一個實現類,此處附 Spring 啟動源碼來了解這個后置處理器。
此處源碼分析, 你可參考:Spring IOC 源碼解析一文學習更清晰。根據代碼,重點:在注冊后置處理器時,Spring IOC 容器已經初始化完成,所以我們可以通過這種方式,在 IOC 容器初始化后,執行一些邏輯操作。

@Override
public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        // Prepare this context for refreshing.
        prepareRefresh();

        //1.此處 obtainFreshBeanFactory()方法,來完成Spring IOC容器的 定位、加載、注冊 等操作
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

        // Prepare the bean factory for use in this context.
        prepareBeanFactory(beanFactory);

        try {
            // Allows post-processing of the bean factory in context subclasses.
            postProcessBeanFactory(beanFactory);

            // Invoke factory processors registered as beans in the context.
            invokeBeanFactoryPostProcessors(beanFactory);

            // Register bean processors that intercept bean creation.
            //2.此處用來注冊配置后置處理器(說明在配置后置處理器時,IOC容器已經創建完成)
            registerBeanPostProcessors(beanFactory);

            //省略部分源碼
        } catch (BeansException ex) {
            // Destroy already created singletons to avoid dangling resources.
            destroyBeans();

            // Reset 'active' flag.
            cancelRefresh(ex);
        } finally {
            resetCommonCaches();
        }
    }
}

示例:(本例使用 Spring Boot框架)

@Component
public class AfterIocInitialConfig implements ApplicationContextAware {

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        //在此處執行邏輯操作
        System.out.println("--ApplicationContextAware-------在此執行一些邏輯操作");
    }


}

2.實現InitializingBean,重寫afterPropertiesSet()方法
(bean 配置文件屬性 init-method 用於在bean初始化時指定執行方法,可以用來替代繼承 InitializingBean接口)
InitializingBean接口為bean提供了初始化方法的方式,它只包括afterPropertiesSet()方法,凡是繼承該接口的類,在初始化 bean 的時候都會執行該方法。(注意:重寫了InitializingBean的 bean 在初始化時才會執行,沒重寫的 bean 是不會被執行的)

@Override
public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        // Prepare this context for refreshing.
        prepareRefresh();

        //1.此處 obtainFreshBeanFactory()方法,來完成Spring IOC容器的 定位、加載、注冊 等操作
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

        // Prepare the bean factory for use in this context.
        prepareBeanFactory(beanFactory);

        try {
            // 省略部分代碼
            
            // Instantiate all remaining (non-lazy-init) singletons.
            //2.初始化所有的單例 bean(實現InitializingBean,重寫afterPropertiesSet()方法,在此處執行)
            finishBeanFactoryInitialization(beanFactory);

            // Last step: publish corresponding event.
            finishRefresh();

            //省略部分源碼
        } catch (BeansException ex) {
            // Destroy already created singletons to avoid dangling resources.
            destroyBeans();

            // Reset 'active' flag.
            cancelRefresh(ex);
        } finally {
            resetCommonCaches();
        }
    }
}

示例:(本例使用 Spring Boot框架)

@Component
public class InitializingBeanAfterIocInitialConfig implements InitializingBean {

    @Override
    public void afterPropertiesSet() throws Exception {
        //在此處執行邏輯操作
        System.out.println("--InitializingBean------在此執行一些邏輯操作");
    }

}

3. 在類中的方法上,添加@PostConstruct注解。(@PreDestroy注銷時使用)
@PostConstruct 說明:
被 @PostConstruct 修飾的方法會在服務器加載 Servlet 的時候運行,並且只會被服務器調用一次,類似於 Servlet 的 init() 方法。被@PostConstruct 修飾的方法會在構造函數之后,init() 方法之前運行。
@PostConstruct 注釋用於在依賴關系注入完成之后需要執行的方法上,以執行任何初始化。此方法必須在將類放入服務之前調用。支持依賴關系注入的所有類都必須支持此注釋。即使類沒有請求注入任何資源,用@PostConstruct 注釋的方法也必須被調用。注意:只有一個方法可以用此注釋進行注釋。
@PreDestroy 說明:
被 @PreDestroy 修飾的方法會在服務器卸載 Servlet 的時候運行,並且只會被服務器調用一次,類似於 Servlet 的 destroy() 方法。被 @PreDestroy 修飾的方法會在 destroy() 方法之后運行,在 Servlet 被徹底卸載之前。
@PreDestroy 注釋作為回調通知用於各方法,以表示該實例正處於被容器移除的過程中。用 @PreDestroy 注釋的方法通常用於釋放它已經持有的資源,所有支持 @PostConstruct 的容器管理對象都必須支持此注釋

  • 應用 @PostConstruct、@PreDestroy 注釋的方法必須遵守以下所有標准:
  • 該方法不得有任何參數,除非是在EJB 攔截器(interceptor)的情況下,它可以帶有一個 InvocationContext 對象;
  • 該方法的返回類型必須為 void;
  • 該方法不得拋出已檢查異常;
  • 應用@PostConstruct 的方法可以使 pulbic、protected、package private 或 private;
  • 除了應用程序客戶端之外,該方法不能是 static;該方法可以是 final;

示例:(本例使用 Spring Boot框架)

@Component
public class PostConstructConfig {

    //在服務器加載 Servlet 的時候執行
    @PostConstruct
    public void doSomething() {
        //在此處執行邏輯操作
        System.out.println("--PostConstruct------在此執行一些邏輯操作");
    }

    //在服務器卸載 Servlet 的時候執行
    @PreDestroy
    public void shutDownDoSomething() {
        System.out.println("--PostConstruct------卸載Servlet時執行");
    }
}

@PostConstruct 注解,顯然和 Spring IOC容器的啟動沒有多大關系,它只和 Servlet 容器的加載有關系。加載 Servlet 時, IOC 容器其實已經初始化完成。所以使用 @PostConstruct 注解,可以滿足 Spring IOC 容器初始化后,執行一些邏輯操作。

4.類實現BeanPostProcessor接口,重寫postProcessBeforeInitialization()、postProcessAfterInitialization()方法
BeanPostProcessor是 Spring IOC 容器給我們提供的一個擴展接口。注意:該接口會在 Spring IOC 容器的每一個 bean 初始化前、后做一些相關操作,記住是每一個!!!。接口聲明如下:

public interface BeanPostProcessor {
    //bean初始化方法調用前被調用
    Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
    //bean初始化方法調用后被調用
    Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}

比如說當前 IOC 容器,有200個bean對象。那么這200個bean對象都會在初始化前、后來調用被重寫的postProcessBeforeInitialization()、postProcessAfterInitialization()方法,相當於是 bean 對象初始化的前置、后置增強。

@Component
public class BeanPostProcessorConfig implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("--BeanPostProcessor--" + beanName + ":對象初始化前執行一些操作");
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("--BeanPostProcessor--" + beanName +":對象初始化后執行一些操作");
        return bean;
    }
}

5.類實現 SmartLifecycle,重寫相關方法
在使用 Spring 開發時,我們都知道,所有 bean都交給 Spring IOC 容器來統一管理,其中包括每一個 bean 的加載和初始化。有時候,我們需要在 Spring 加載和初始化所有bean后,接着執行一些任務或者啟動需要的異步服務,這樣我們可以使用 SmartLifecycle 來做到。SmartLifecycle 是一個接口,繼承自 Lifecycle(生命周期)、Phased(如果有多個實現 Liftcycle接口的類,通過getPhase()方法來確定執行先后順序)兩個接口。當 Spring 容器加載所有 bean 並完成初始化之后,會接着回調實現該接口的類中對應的方法(start()方法)。
LifecycleProcessor 處理器,在源碼中的執行位置,如下所示:

@Override
public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        // Prepare this context for refreshing.
        prepareRefresh();

        // Tell the subclass to refresh the internal bean factory.
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

        // Prepare the bean factory for use in this context.
        prepareBeanFactory(beanFactory);

        try {
            // Allows post-processing of the bean factory in context subclasses.
            postProcessBeanFactory(beanFactory);

            //省略

            // Last step: publish corresponding event.
            // 重點:LifecycleProcessor 在此處被調用(繼續進入方法查看)
            finishRefresh();
        }

        catch (BeansException ex) {
            //省略
        }

        finally {
            resetCommonCaches();
        }
    }
}


protected void finishRefresh() {

    // Initialize lifecycle processor for this context.
    // 重點1:為當前context初始化 LifecycleProcessor()
    initLifecycleProcessor();

    // Propagate refresh to lifecycle processor first.
    // 重點2:調用 lifecycleProcessor 的 onRefresh() 方法(如果我們定義了一個類,實現了 lifecycle 相關接口,則會在此處調用自定義的 onRefresh() 方法)
    getLifecycleProcessor().onRefresh();

    // Publish the final event.
    // 事件機制發布(在下一種實現方式 ApplicationContextListener 中會涉及到)
    publishEvent(new ContextRefreshedEvent(this));

    // Participate in LiveBeansView MBean, if active.
    LiveBeansView.registerApplicationContext(this);
}

如果IOC容器沒有 lifecycleProcessor,則會使用 DefaultLifecycleProcessor 類,此處附DefaultLifecycleProcessor 類源碼(省略部分源碼)

public class DefaultLifecycleProcessor implements LifecycleProcessor, BeanFactoryAware {

    private volatile long timeoutPerShutdownPhase = 30000;

    private volatile boolean running;
    
    /**
     * Specify the maximum time allotted in milliseconds for the shutdown of
     * any phase (group of SmartLifecycle beans with the same 'phase' value).
     * <p>The default value is 30 seconds.
     */
    public void setTimeoutPerShutdownPhase(long timeoutPerShutdownPhase) {
        this.timeoutPerShutdownPhase = timeoutPerShutdownPhase;
    }
    
    /**
     * Start all registered beans that implement {@link Lifecycle} and are <i>not</i>
     * already running. Any bean that implements {@link SmartLifecycle} will be
     * started within its 'phase', and all phases will be ordered from lowest to
     * highest value. All beans that do not implement {@link SmartLifecycle} will be
     * started in the default phase 0. A bean declared as a dependency of another bean
     * will be started before the dependent bean regardless of the declared phase.
     */
    @Override
    public void start() {
        startBeans(false);
        this.running = true;
    }

    /**
     * Stop all registered beans that implement {@link Lifecycle} and <i>are</i>
     * currently running. Any bean that implements {@link SmartLifecycle} will be
     * stopped within its 'phase', and all phases will be ordered from highest to
     * lowest value. All beans that do not implement {@link SmartLifecycle} will be
     * stopped in the default phase 0. A bean declared as dependent on another bean
     * will be stopped before the dependency bean regardless of the declared phase.
     */
    @Override
    public void stop() {
        stopBeans();
        this.running = false;
    }

    @Override
    public void onRefresh() {
        startBeans(true);
        this.running = true;
    }

    @Override
    public void onClose() {
        stopBeans();
        this.running = false;
    }

    @Override
    public boolean isRunning() {
        return this.running;
    }
}

示例:(本例使用 Spring Boot框架)

@Component
public class SmartLifecycleConfig implements SmartLifecycle, ApplicationContextAware {


    private boolean isRunning = false;

    private ApplicationContext context = null;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.context = applicationContext;
    }

    /**
     * 1. 我們主要在該方法中啟動任務或者其他異步服務,比如開啟MQ接收消息
     * 2. 當上下文被刷新(所有對象已被實例化和初始化之后)時,將調用該方法,默認生命周期處理器將檢查每個SmartLifecycle對象的isAutoStartup()方法返回的布爾值。
     * 如果為“true”,則該方法會被調用,而不是等待顯式調用自己的start()方法。
     */
    @Override
    public void start() {
        System.out.println("---SmartLifecycle--執行start()方法");
        // 執行完其他業務后,可以修改 isRunning = true
        isRunning = true;
    }

    /**
     * 如果工程中有多個實現接口SmartLifecycle的類,則這些類的start的執行順序按getPhase方法返回值從小到大執行。
     * 例如:1比2先執行,-1比0先執行。 stop方法的執行順序則相反,getPhase返回值較大類的stop方法先被調用,小的后被調用。
     */
    @Override
    public int getPhase() {
        // 默認為0
        System.out.println("---SmartLifecycle--執行getPhase方法");
        return 0;
    }

    /**
     * 根據該方法的返回值決定是否執行start方法。
     * 返回true時start方法會被自動執行,返回false則不會。
     */
    @Override
    public boolean isAutoStartup() {
//        return false;
        System.out.println("---SmartLifecycle--執行 isAutoStartup 方法");
        return true;
    }

    /**
     * SmartLifecycle子類才有的方法,當isRunning方法返回true時,該方法才會被調用。
     */
   /*@Override
    public void stop(Runnable callback) {
        System.out.println("stop(Runnable)");

        // 如果你讓isRunning返回true,需要執行stop這個方法,那么就不要忘記調用callback.run()。
        // 否則在你程序退出時,Spring的DefaultLifecycleProcessor會認為你這個TestSmartLifecycle沒有stop完成,程序會一直卡着結束不了,等待一定時間(默認超時時間30秒)后才會自動結束。
        // PS:如果你想修改這個默認超時時間,可以按下面思路做,當然下面代碼是springmvc配置文件形式的參考,在SpringBoot中自然不是配置xml來完成,這里只是提供一種思路。
        // <bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor">
        //      <!-- timeout value in milliseconds -->
        //      <property name="timeoutPerShutdownPhase" value="10000"/>
        // </bean>
        callback.run();

        isRunning = false;
    }*/


    /**
     * 1. 只有該方法返回false時,start方法才會被執行。
     * 2. 只有該方法返回true時,stop(Runnable callback)或stop()方法才會被執行。
     */
    @Override
    public boolean isRunning() {
        // 默認返回false
        System.out.println("---SmartLifecycle--執行 isRunning 方法");
        return isRunning;
    }

    /**
     * 接口Lifecycle的子類的方法,只有非SmartLifecycle的子類才會執行該方法。<br/>
     * 1. 該方法只對直接實現接口Lifecycle的類才起作用,對實現SmartLifecycle接口的類無效。<br/>
     * 2. 方法stop()和方法stop(Runnable callback)的區別只在於,后者是SmartLifecycle子類的專屬。
     */
    @Override
    public void stop() {
        System.out.println("---SmartLifecycle--執行 stop() 方法");
        isRunning = false;
    }


}

6.實現ApplicationContextListener<ContextRefreshedEvent>,重寫onApplicationEvent()方法
ApplicationContextListener 事件機制是觀察者設計模式的實現,通過ApplicationEvent類和ApplicationListener接口,可以實現ApplicationContext事件處理;如果容器中存在 ApplicationListener 的Bean,當 ApplicationContext 調用 publishEvent 方法時,對應的Bean會被觸發。publishEvent() 方法,同剛剛介紹的 Lifecycle 類似,也是在 finishRefresh() 方法中調用的。如下所示:

protected void finishRefresh() {

    // Initialize lifecycle processor for this context.
    initLifecycleProcessor();

    // Propagate refresh to lifecycle processor first.
    getLifecycleProcessor().onRefresh();

    // Publish the final event.
    // 重點:事件機制發布(在下一種實現方式 ApplicationContextListener 中會涉及到)
    publishEvent(new ContextRefreshedEvent(this));

    // Participate in LiveBeansView MBean, if active.
    LiveBeansView.registerApplicationContext(this);
}

其中 Spring 有一些內置的事件,當完成某種操作時會發出某些事件動作。比如監聽ContextRefreshedEvent事件,當所有的bean都初始化完成並被成功裝載后會觸發該事件,實現ApplicationListener<ContextRefreshedEvent>接口可以收到監聽動作,然后可以寫自己的邏輯。同樣事件我們可以自定義、監聽也可以自定義,完全根據自己的業務邏輯來處理。

示例:(本例使用 Spring Boot框架)

@Component
public class ApplicationListenerConfig implements ApplicationListener<ContextRefreshedEvent> {

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        System.out.println("--ApplicationListener--監聽到所有的bean都已經初始化完成");
    }

}

附錄:

  • 我們可以監聽 ServletWebServerInitializedEvent 類,來監聽 Servlet 是否初始化完成;
  • 也可以監聽 ApplicationReadyEvent 類,來監聽應用(項目)是否啟動完成。

7.類實現ApplicationRunner,重寫run()方法
在開發中可能會有這樣的情景。需要在容器啟動的時候執行一些內容。比如讀取配置文件,數據庫連接之類的。Spring 項目中,我們可以通過實現 ApplicationRunner接口,重寫run() 方法。它的執行時機為容器啟動完成的時候。
示例:(本例使用 Spring Boot框架)

@Component
public class ApplicationRunnerConfig implements ApplicationRunner {

    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("--ApplicationRunner---當前項目已經啟動完成");
    }   

}

8.類實現CommandLineRunner,重寫run()方法
SpringBoot 中額外給我們提供了一個接口,這個接口是 CommandLineRunner。它和 ApplicationRunner功能一樣,也都需要重寫run()方法。該接口也是在容器啟動完成的時候執行。
它們的不同之處在於:(其它使用都相同)
ApplicationRunner 可以在 Spring、SpringBoot 項目中使用;CommandLineRunner 只能在 Spring Boot 中使用
ApplicationRunner 中 run() 方法的參數為 ApplicationArguments,而 CommandLineRunner 中 run() 方法的參數為 String數組。
示例:(本例使用 Spring Boot框架)

@Component
public class CommandLineRunnerConfig implements CommandLineRunner {

    @Override
    public void run(String... args) throws Exception {
        System.out.println("--CommandLineRunner---當前項目已經啟動完成");
    }

}

總結:執行順序還是按照1、2、3、4、5、6、7、8的順序進行加載的,每個實現都有不同的方式和不同的目的,同時需要了解每個實現的原理是什么,什么場景的時候使用。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM