@postconstruct VS @eventListener 以及 dubbo服務暴露(1)

看標題,你可能並太可能知道我想要說什麼,但是肯定和這3個關鍵字有關係。
這一切都要從線上一個服務的報錯開始:

背景

線上報錯表現:服務啓動時候一個定時任務卡住了,進而導致服務起不來。
當時這個足足卡了有5分鐘多,可見並不是任務執行時間久(平時也就最多30s)

分析

連忙相辦法找運維同學把線上該服務的堆棧信息拿到手,發現有線程blocked,這個線程就是加載定時任務,去redis裏面get數據的。

在這裏插入圖片描述
如上圖可見,是最後獲取spring bean的時候死鎖了。

spring 初始化相關點
ApplicationRunner與CommandLineRunner接口

如果需要在SpringApplication啓動時執行一些特殊的代碼,你可以實現ApplicationRunner或CommandLineRunner接口,這兩個接口工作方式相同,都只提供單一的run方法,而且該方法僅在SpringApplication.run(…)完成之前調用,更準確的說是在構造SpringApplication實例完成之後調用run()的時候,具體分析見後文,所以這裏將他們分爲一類。
構造一個類實現ApplicationRunner接口

@Component
public class ApplicationRunnerTest implements ApplicationRunner {

    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("ApplicationRunner");
    }
}

CommandLineRunner
對於這兩個接口而言,我們可以通過Order註解或者使用Ordered接口來指定調用順序,@Order()中的值越小,優先級越高

@Component
@Order(1)
public class CommandLineRunnerTest implements CommandLineRunner {

    @Override
    public void run(String... args) throws Exception {
        System.out.println("CommandLineRunner...");
    }
}


兩者的聯繫與區別
前面就提到過,兩個接口都有run()方法,只不過它們的參數不一樣,CommandLineRunner的參數是最原始的參數,沒有進行任何處理,ApplicationRunner的參數是ApplicationArguments,是對原始參數的進一步封裝

接下來我們簡要跟蹤一下源碼看ApplicationRunner(CommandLineRunner)是如何被調用的。
Springboot在啓動的時候,都會構造一個SpringApplication實例,至於這個實例怎麼構造的,這裏不去探究了,有感興趣的可以去看下源碼。這裏主要看ApplicationRunner是如何被調用的,而它的調用就是在SpringApplication這個實例調用run方法中。

@SpringBootApplication
public class Application {


    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

進入run方法

	public static ConfigurableApplicationContext run(Class<?> primarySource,
			String... args) {
		return run(new Class<?>[] { primarySource }, args);
	}


執行SpringApplication的run方法

	public static ConfigurableApplicationContext run(Class<?>[] primarySources,
			String[] args) {
		return new SpringApplication(primarySources).run(args);
	}


在這裏插入圖片描述

發現對ApplicationRunner的調用實際上在callRunners方法中
在這裏插入圖片描述

對於CommandLineRunner或者ApplicationRunner來說,需要注意的兩點:

所有CommandLineRunner/ApplicationRunner的執行時點是在SpringBoot應用的ApplicationContext完全初始化開始工作之後,callRunners()可以看出是run方法內部最後一個調用的方法(可以認爲是main方法執行完成之前最後一步)
只要存在於當前SpringBoot應用的ApplicationContext中的任何CommandLineRunner/ApplicationRunner,都會被加載執行(不管你是手動註冊還是自動掃描去Ioc容器)

Spring Bean初始化的InitializingBean,init-method和PostConstruct

InitializingBean接口爲bean提供了初始化方法的方式,它只包括afterPropertiesSet()方法。
在spring初始化bean的時候,如果bean實現了InitializingBean接口,在對象的所有屬性被初始化後之後纔會調用afterPropertiesSet()方法

@Component
public class InitialingzingBeanTest implements InitializingBean {

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("InitializingBean..");
    }
}

在這裏插入圖片描述

當然,我們可以看出spring初始化bean肯定會在
ApplicationRunner和CommandLineRunner接口調用之前。
當然有一點我們要注意的是,儘管使用initialingBean接口可以實現初始化動作,但是官方並不建議我們使用InitializingBean接口,因爲它將你的代碼耦合在Spring代碼中,官方的建議是在bean的配置文件指定init-method方法,或者在@Bean中設置init-method屬性

在這裏插入圖片描述

init-method和@PostConstruct

前面就說過官方文檔上不建議使用InitializingBean接口,但是我們可以在元素的init-method屬性指定bean初始化之後的操作方法,或者在指定方法上加上@PostConstruct註解來制定該方法在初始化之後調用

Spring的事件機制

Spring的事件機制實際上是設計模式中觀察者模式的典型應用,在Head First 設計模式中是這樣定義觀察者模式的:

觀察者模式定義了一個一對多的依賴關係,讓一個或多個觀察者對象監聽一個主題對象。這樣一來,當被觀察者狀態改變時,需要通知相應的觀察者,使這些觀察者能夠自動更新
在這裏插入圖片描述

基礎概念

Spring的事件驅動模型由三部分組成

事件: ApplicationEvent,繼承自JDK的EventObject,所有事件都要繼承它,也就是被觀察者
事件發佈者: ApplicationEventPublisher及ApplicationEventMulticaster接口,使用這個接口,就可以發佈事件了
事件監聽者: ApplicationListener,繼承JDK的EventListener,所有監聽者都繼承它,也就是我們所說的觀察者,當然我們也可以使用註解 @EventListener,效果是一樣的

事件

在Spring框架中,默認對ApplicationEvent事件提供瞭如下支持:

ContextStartedEvent:ApplicationContext啓動後觸發的事件
ContextStoppedEvent:ApplicationContext停止後觸發的事件
ContextRefreshedEvent:ApplicationContext初始化或刷新完成後觸發的事件;(容器初始化完成後調用,所以我們可以利用這個事件做一些初始化操作)
ContextClosedEvent:ApplicationContext關閉後觸發的事件;(如web容器關閉時自動會觸發spring容器的關閉,如果是普通java應用,需要調用ctx.registerShutdownHook();註冊虛擬機關閉時的鉤子纔行)
在這裏插入圖片描述

構造一個類繼承ApplicationEvent

public class TestEvent extends ApplicationEvent {

    private static final long serialVersionUID = -376299954511699499L;
    private String message;
    /**
     * Create a new ApplicationEvent.
     *
     * @param source the object on which the event initially occurred (never {@code null})
     */
    public TestEvent(Object source) {
        super(source);
    }

    public void getMessage() {
        System.out.println(message);
    }

    public void setMessage(String message) {
        this.message = message;
    }

}


創建事件監聽者

有兩種方法可以創建監聽者,一種是直接實現ApplicationListener的接口,一種是使用註解 @EventListener,註解是添加在監聽方法上的,下面的例子是直接實現的接口

@Component
public class ApplicationListenerTest implements ApplicationListener<TestEvent> {
    @Override
    public void onApplicationEvent(TestEvent testEvent)
    {
        testEvent.getMessage();
    }
}

事件發佈

對於事件發佈,代表者是ApplicationEventPublisher和ApplicationEventMulticaster,系統提供的實現如下

在這裏插入圖片描述
ApplicationContext接口繼承了ApplicationEventPublisher,並在AbstractApplicationContext實現了具體代碼,實際執行是委託給ApplicationEventMulticaster(可以認爲是多播)
下面是一個事件發佈者的測試實例:

@RunWith(SpringRunner.class)
@SpringBootTest
public class EventTest {
    @Autowired
    private ApplicationContext applicationContext;

    @Test
    public void publishTest() {
        TestEvent testEvent = new TestEvent("");
        testEvent.setMessage("hello world");
        applicationContext.publishEvent(testEvent);
    }
}
//output:
hello world

利用ContextRefreshedEvent事件進行初始化操作

利用Spring的事件機制進行初始化一些操作,實際上就是前面提到了,利用ContextRefreshedEvent事件進行初始化,該事件是ApplicationContext初始化完成後調用的事件,所以我們可以利用這個事件,對應實現一個監聽器,在其onApplicationEvent()方法裏初始化操作.

@Component
public class ApplicationListenerTest implements ApplicationListener<ContextRefreshedEvent> {

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        System.out.println("我被調用了..");
    }
}

在這裏插入圖片描述

以上簡要總結了在springboot啓動時進行初始化操作的幾個方案,這幾種方式都可以滿足我們的需求,針對具體場景使用對應的方案。但是,CommandLineRunner或者ApplicationRunner不是Spring框架原有的東西,它倆屬於SpringBoot應用特定的回調擴展接口,所以很容易進行擴展,在一些微服務應用中使用也較廣泛。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章