1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        SpringBoot 啟動時初始化數(shù)據(jù)

        共 8605字,需瀏覽 18分鐘

         ·

        2021-09-19 15:04

        0x01:前言

        在使用 springboot 搭建項目的時候,有時候會碰到在項目啟動時初始化一些操作的需求,針對這種需求 springboot(spring) 提供了以下幾種方案:

        • ApplicationRunner 與 CommandLineRunner 接口

        • Spring Bean 初始化的I nitializingBean, init-method 和 PostConstruct

        • Spring 的事件機(jī)制


        0x02:ApplicationRunner與CommandLineRunner

        如果需要在SpringApplication啟動時執(zhí)行一些特殊的代碼,可以實現(xiàn)ApplicationRunner 或 CommandLineRunner 接口,這兩個接口工作方式相同,都只提供單一的 run() 方法,而且該方法僅在SpringApplication.run(…)完成之前調(diào)用,更準(zhǔn)確的說是在構(gòu)造 SpringApplication 實例完成之后調(diào)用 run() 的時候,具體分析見后文,所以這里將他們分為一類。

        ApplicationRunner

        構(gòu)造一個類實現(xiàn)ApplicationRunner接口

        @Component
        public class ApplicationRunnerTest implements ApplicationRunner {

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

        CommandLineRunner

        對于這兩個接口而言,可以通過 Order 注解或者使用 Ordered 接口來指定調(diào)用順序,@Order() 中的值越小,優(yōu)先級越高

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

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

        當(dāng)然也可以同時使用 ApplicationRunner 和 CommandLineRunner,默認(rèn)情況下前者比后者先執(zhí)行,但是這沒有必要,使用一個就可以了。

        兩者的聯(lián)系與區(qū)別

        這兩個接口都有 run() 方法,只不過它們的參數(shù)不一樣,CommandLineRunne r的參數(shù)是最原始的參數(shù),沒有進(jìn)行任何處理,ApplicationRunner 的參數(shù)是ApplicationArguments, 是對原始參數(shù)的進(jìn)一步封裝。

        簡要跟蹤一下源碼看 ApplicationRunner(CommandLineRunner) 是如何被調(diào)用的。

        Springboot 在啟動的時候,都會構(gòu)造一個 SpringApplication 實例,至于這個實例怎么構(gòu)造的,這里不去探究了,有感興趣的可以去看下源碼。這里主要看ApplicationRunner 是如何被調(diào)用的,而它的調(diào)用就是在SpringApplication這個實例調(diào)用run方法中。

        @SpringBootApplication
        public class Application {

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

        進(jìn)入run方法

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

        執(zhí)行 SpringApplication 的 run 方法

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

        一路點擊 run() 來,發(fā)現(xiàn)對 ApplicationRunner 的調(diào)用實際上在 callRunners 方法中

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

        • 所有 CommandLineRunner / ApplicationRunner 的執(zhí)行時點是在 SpringBoot 應(yīng)用的 ApplicationContext 完全初始化開始工作之后,callRunners() 可以看出是 run 方法內(nèi)部最后一個調(diào)用的方法(可以認(rèn)為是main方法執(zhí)行完成之前最后一步)

        • 只要存在于當(dāng)前 SpringBoot 應(yīng)用的 ApplicationContext 中的任何CommandLineRunner / ApplicationRunner,都會被加載執(zhí)行(不管你是手動注冊還是自動掃描去Ioc容器)


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

        InitializingBean接口

        InitializingBean 接口為 bean 提供了初始化方法的方式,它只包括 afterPropertiesSet()方法。

        在 spring 初始化 bean 的時候,如果bean實現(xiàn)了 InitializingBean 接口,在對象的所有屬性被初始化后之后才會調(diào)用 afterPropertiesSet() 方法

        @Component
        public class InitialingzingBeanTest implements InitializingBean {

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

        當(dāng)然,可以看出spring初始化bean肯定會在 ApplicationRunner 和 CommandLineRunner 接口調(diào)用之前。

        當(dāng)然有一點要注意的是,盡管使用 InitialingBean 接口可以實現(xiàn)初始化動作,但是官方并不建議使用 InitializingBean 接口,因為它將你的代碼耦合在Spring代碼中,官方的建議是在 bean 的配置文件指定 init-method 方法,或者在 @Bean 中設(shè)置 init-method 屬性

        init-method和@PostConstruct

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

        @SpringBootApplication
        public class Application {

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

            @PostConstruct
            public void init() {
                System.out.println("init...");
            }
        }

        更多關(guān)于Spring Bean的生命周期的內(nèi)容,請參閱Spring相關(guān)書籍或博客Spring Bean的生命周期


        0x04:Spring的事件機(jī)制

        Spring 的事件機(jī)制實際上是設(shè)計模式中觀察者模式的典型應(yīng)用。

        觀察者模式定義了一個一對多的依賴關(guān)系,讓一個或多個觀察者對象監(jiān)聽一個主題對象。這樣一來,當(dāng)被觀察者狀態(tài)改變時,需要通知相應(yīng)的觀察者,使這些觀察者能夠自動更新

        基礎(chǔ)概念

        Spring的事件驅(qū)動模型由三部分組成

        • 事件: ApplicationEvent,繼承自JDK的EventObject,所有事件都要繼承它,也就是被觀察者

        • 事件發(fā)布者: ApplicationEventPublisher及ApplicationEventMulticaster接口,使用這個接口,就可以發(fā)布事件了

        • 事件監(jiān)聽者: ApplicationListener,繼承JDK的EventListener,所有監(jiān)聽者都繼承它,也就是我們所說的觀察者,當(dāng)然我們也可以使用注解 @EventListener,效果是一樣的

        事件

        在Spring框架中,默認(rèn)對ApplicationEvent事件提供了如下支持:

        • ContextStartedEvent:ApplicationContext啟動后觸發(fā)的事件

        • ContextStoppedEvent:ApplicationContext停止后觸發(fā)的事件

        • ContextRefreshedEvent:ApplicationContext初始化或刷新完成后觸發(fā)的事件;(容器初始化完成后調(diào)用,所以我們可以利用這個事件做一些初始化操作)

        • ContextClosedEvent:ApplicationContext關(guān)閉后觸發(fā)的事件;(如web容器關(guān)閉時自動會觸發(fā)spring容器的關(guān)閉,如果是普通java應(yīng)用,需要調(diào)用ctx.registerShutdownHook();注冊虛擬機(jī)關(guān)閉時的鉤子才行)

        構(gòu)造一個類繼承ApplicationEvent

        public class TestEvent extends ApplicationEvent {

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

        }

        創(chuàng)建事件監(jiān)聽者

        有兩種方法可以創(chuàng)建監(jiān)聽者,一種是直接實現(xiàn)ApplicationListener的接口,一種是使用注解 @EventListener,注解是添加在監(jiān)聽方法上的,下面的例子是直接實現(xiàn)的接口

        @Component

        public class ApplicationListenerTest implements ApplicationListener<TestEvent{

            @Override
            public void onApplicationEvent(TestEvent testEvent){
                testEvent.getMessage();
            }

        }

        事件發(fā)布

        對于事件發(fā)布,代表者是ApplicationEventPublisher和ApplicationEventMulticaster,

        ApplicationContext接口繼承了ApplicationEventPublisher,并在AbstractApplicationContext實現(xiàn)了具體代碼,實際執(zhí)行是委托給ApplicationEventMulticaster(可以認(rèn)為是多播)

        下面是一個事件發(fā)布者的測試實例:

        @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事件進(jìn)行初始化操作

        前面做了這么多鋪墊,下面進(jìn)入今天的主題,利用Spring的事件機(jī)制進(jìn)行初始化一些操作,實際上就是前面提到了,利用ContextRefreshedEvent事件進(jìn)行初始化,該事件是ApplicationContext初始化完成后調(diào)用的事件,所以我們可以利用這個事件,對應(yīng)實現(xiàn)一個監(jiān)聽器,在其onApplicationEvent()方法里初始化操作

        @Component
        public class ApplicationListenerTest implements ApplicationListener<ContextRefreshedEvent{

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

        }

        注意: 在傳統(tǒng)的基于XML配置的Spring項目中會存在二次調(diào)用的問題,即調(diào)用兩次該方法,原因是在傳統(tǒng)的Spring MVC項目中,系統(tǒng)存在兩個容器,一個root容器,一個project-servlet.xml對應(yīng)的子容器,在初始化這兩個容器的時候都會調(diào)用該方法一次,所以有二次調(diào)用的問題,而對于基于Springboot的項目不存在這個問題


        小結(jié)

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

        source: //pjmike.github.io/2018/08/16/springboot系列文章之啟動時初始化數(shù)據(jù)/

        喜歡,在看


        瀏覽 46
        點贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            A级视频网站 | 色戒免费高清电影观看视频在线播放下载 | 天堂v视频 | 色狠久久AV翔田千里 | 欧美X X888做受 | 伊人色播| 囯产精品久久久久久久久久辛辛 | 极品粉嫩小泬XXXXHD11 | 久久精品蜜桃 | 一区二 |