<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          了解這些,你就可以在Spring啟動時為所欲為了

          共 7747字,需瀏覽 16分鐘

           ·

          2021-01-28 14:15

          八仙過海,各顯神通

          Spring 是一個控制反轉(zhuǎn)依賴管理的容器,作為 Java Web 的開發(fā)人員,基本沒有不熟悉 Spring 技術(shù)棧的,盡管在依賴注入領(lǐng)域,Java Web 領(lǐng)域不乏其他優(yōu)秀的框架,如 google 開源的依賴管理框架 guice,如 Jersey web 框架等。但 Spring 已經(jīng)是 Java Web 領(lǐng)域使用最多,應(yīng)用最廣泛的 Java 框架。

          此文將專注講解如何在 Spring 容器啟動時實現(xiàn)我們自己想要實現(xiàn)的邏輯。我們時常會遇到在 Spring 啟動的時候必須完成一些初始化的操作,如創(chuàng)建定時任務(wù),創(chuàng)建連接池等。

          本文將介紹以下幾種 Spring 啟動監(jiān)聽方式:

          • Bean 構(gòu)造函數(shù)方式
          • 使用 @PostConstruct 注解
          • 實現(xiàn) InitializingBean 接口
          • 監(jiān)聽 ApplicationListener 事件
          • 使用 Constructor 注入方式
          • 實現(xiàn) SpringBoot 的 CommandLineRunner 接口
          • SmartLifecycle 機(jī)制

          原始構(gòu)造函數(shù)

          如果沒有 Spring 容器,不依賴于 Spring 的實現(xiàn),回歸 Java 類實現(xiàn)本身,我們可以在靜態(tài)代碼塊,在類構(gòu)造函數(shù)中實現(xiàn)相應(yīng)的邏輯,Java 類的初始化順序依次是靜態(tài)變量 > 靜態(tài)代碼塊 > 全局變量 > 初始化代碼塊 > 構(gòu)造器

          比如,Log4j 的初始化,就是在 LogManager 的靜態(tài)代碼塊中實現(xiàn)的:


          static?{

          ????Hierarchy?h?=?new?Hierarchy(new?RootLogger((Level)?Level.DEBUG));
          ????repositorySelector?=?new?DefaultRepositorySelector(h);

          ????String?override?=OptionConverter.getSystemProperty(DEFAULT_INIT_OVERRIDE_KEY,null);

          ????if(override?==?null?||?"false".equalsIgnoreCase(override))?{
          ??????????String?configurationOptionStr?=?OptionConverter.getSystemProperty(DEFAULT_CONFIGURATION_KEY,?null);
          ??????????String?configuratorClassName?=?OptionConverter.getSystemProperty(CONFIGURATOR_CLASS_KEY,?null);

          ??????????URL?url?=?null;

          ??????????if(configurationOptionStr?==?null)?{
          ????????????url?=?Loader.getResource(DEFAULT_XML_CONFIGURATION_FILE);
          ????????????if(url?==?null)?{
          ??????????????url?=?Loader.getResource(DEFAULT_CONFIGURATION_FILE);
          ????????????}
          ??????????}?else?{
          ????????????try?{
          ??????????????url?=?new?URL(configurationOptionStr);
          ????????????}?catch?(MalformedURLException?ex)?{
          ??????????????url?=?Loader.getResource(configurationOptionStr);
          ????????????}
          ??????????}

          ??????????if(url?!=?null)?{
          ????????????LogLog.debug("Using?URL?["+url+"]?for?automatic?log4j?configuration.");
          ????????????try?{
          ????????????????OptionConverter.selectAndConfigure(url,?configuratorClassName,LogManager.getLoggerRepository());
          ????????????}?catch?(NoClassDefFoundError?e)?{
          ????????????????LogLog.warn("Error?during?default?initialization",?e);
          ????????????}
          ??????????}?else?{
          ??????????????LogLog.debug("Could?not?find?resource:?["+configurationOptionStr+"].");
          ??????????}
          ????}?else?{
          ????????????LogLog.debug("Default?initialization?of?overridden?by?"?+??DEFAULT_INIT_OVERRIDE_KEY?+?"property.");
          ????}
          }

          比如在構(gòu)造函數(shù)中實現(xiàn)相應(yīng)的邏輯:

          @Component
          public?class?CustomBean?{

          ????@Autowired
          ????private?Environment?env;

          ????public?CustomBean()?{
          ????????env.getActiveProfiles();
          ????}
          }

          這里考驗一下各位,上面的代碼是否可以正常運行。—— 不行,構(gòu)造函數(shù)中的env將會發(fā)生NullPointException異常。這是因為在 Spring 中將先初始化 Bean,也就是會先調(diào)用類的構(gòu)造函數(shù),然后才注入成員變量依賴的 Bean(@Autowired@Resource注解修飾的成員變量),注意@Value等注解的配置的注入也是在構(gòu)造函數(shù)之后。

          @PostConstruct

          在 Spring 中,我們可以使用@PostConstruct在 Bean 初始化之后實現(xiàn)相應(yīng)的初始化邏輯,@PostConstruct修飾的方法將在 Bean 初始化完成之后執(zhí)行,此時 Bean 的依賴也已經(jīng)注入完成,因此可以在方法中調(diào)用注入的依賴 Bean。

          @Component
          public?class?CustomBean?{

          ????@Autowired
          ????private?Environment?env;

          ????@PostConstruce
          ????public?void?init()?{
          ????????env.getActiveProfiles();
          ????}
          }

          @PostConstruct相對應(yīng)的,如果想在 Bean 注銷時完成一些清掃工作,如關(guān)閉線程池等,可以使用@PreDestroy注解:

          @Component
          public?class?CustomBean?{

          ????@Autowired
          ????private?ExecutorService?executor?=?Executors.newFixedThreadPool(1)

          ????@PreDestroy
          ????public?void?destroy()?{
          ????????env.getActiveProfiles();
          ????}
          }

          InitializingBean

          實現(xiàn) Spring 的InitializingBean接口同樣可以實現(xiàn)以上在 Bean 初始化完成之后執(zhí)行相應(yīng)邏輯的功能,實現(xiàn)InitializingBean接口,在afterPropertiesSet方法中實現(xiàn)邏輯:

          @Component
          public?class?CustomBean?implements?InitializingBean?{

          ????private?static?final?Logger?LOG
          ??????=?Logger.getLogger(InitializingBeanExampleBean.class);

          ????@Autowired
          ????private?Environment?environment;

          ????@Override
          ????public?void?afterPropertiesSet()?throws?Exception?{
          ????????LOG.info(environment.getDefaultProfiles());
          ????}
          }

          ApplicationListener

          我們可以在 Spring 容器初始化的時候?qū)崿F(xiàn)我們想要的初始化邏輯。這時我們就可以使用到 Spring 的初始化事件。Spring 有一套完整的事件機(jī)制,在 Spring 啟動的時候,Spring 容器本身預(yù)設(shè)了很多事件,在 Spring 初始化的整個過程中在相應(yīng)的節(jié)點觸發(fā)相應(yīng)的事件,我們可以通過監(jiān)聽這些事件來實現(xiàn)我們的初始化邏輯。Spring 的事件實現(xiàn)如下:

          • ApplicationEvent,事件對象,由 ApplicationContext 發(fā)布,不同的實現(xiàn)類代表不同的事件類型。
          • ApplicationListener,監(jiān)聽對象,任何實現(xiàn)了此接口的 Bean 都會收到相應(yīng)的事件通知。實現(xiàn)了 ApplicationListener 接口之后,需要實現(xiàn)方法 onApplicationEvent(),在容器將所有的 Bean 都初始化完成之后,就會執(zhí)行該方法。

          與 Spring Context 生命周期相關(guān)的幾個事件有以下幾個:

          • ApplicationStartingEvent: 這個事件在 Spring Boot 應(yīng)用運行開始時,且進(jìn)行任何處理之前發(fā)送(除了監(jiān)聽器和初始化器注冊之外)。
          • ContextRefreshedEvent: ApplicationContext 被初始化或刷新時,該事件被發(fā)布。這也可以在 ConfigurableApplicationContext 接口中使用 refresh() 方法來發(fā)生。
          • ContextStartedEvent: 當(dāng)使用 ConfigurableApplicationContext 接口中的 start() 方法啟動 ApplicationContext 時,該事件被觸發(fā)。你可以查詢你的數(shù)據(jù)庫,或者你可以在接受到這個事件后重啟任何停止的應(yīng)用程序。
          • ApplicationReadyEvent: 這個事件在任何 application/ command-line runners 調(diào)用之后發(fā)送。
          • ContextClosedEvent: 當(dāng)使用 ConfigurableApplicationContext 接口中的 close() 方法關(guān)閉 ApplicationContext 時,該事件被觸發(fā)。一個已關(guān)閉的上下文到達(dá)生命周期末端;它不能被刷新或重啟。
          • ContextStoppedEvent: Spring 最后完成的事件。

          因此,如果我們想在 Spring 啟動的時候?qū)崿F(xiàn)一些相應(yīng)的邏輯,可以找到 Spring 啟動過程中符合我們需要的事件,通過監(jiān)聽相應(yīng)的事件來完成我們的邏輯:

          @Component
          @Slf4j
          public?class?StartupApplicationListenerExample?implements?ApplicationListener<ContextRefreshedEvent>?{

          ????@Override
          ????public?void?onApplicationEvent(ContextRefreshedEvent?event)?{
          ????????log.info("Subject?ContextRefreshedEvent");
          ????}
          }

          除了通過實現(xiàn)ApplicationListener接口來監(jiān)聽相應(yīng)的事件,Spring 的事件機(jī)制也實現(xiàn)了通過@EventListener注解來監(jiān)聽相對應(yīng)事件:

          @Component
          @Slf4j
          public?class?StartupApplicationListenerExample?{

          ????@EventListener
          ????public?void?onApplicationEvent(ContextRefreshedEvent?event)?{
          ????????log.info("Subject?ContextRefreshedEvent");
          ????}
          }

          Spring Event 是一套完善的進(jìn)程內(nèi)事件發(fā)布訂閱機(jī)制,我們除了用來監(jiān)聽 Spring 內(nèi)置的事件,也可以使用 Spring Event 實現(xiàn)自定義的事件發(fā)布訂閱功能。

          Constructor 注入

          在學(xué)習(xí) Spring 的注入機(jī)制的時候,我們都知道 Spring 可以通過構(gòu)造函數(shù)、Setter 和反射成員變量注入等方式。上面我們在成員變量上通過@Autoware注解注入依賴 Bean,但是在 Bean 的構(gòu)造函數(shù)函數(shù)中卻無法使用到注入的 Bean(因為 Bean 還未注入),其實我們也是使用 Spring 的構(gòu)造函數(shù)注入方式, 這也是 Spring 推薦的注入機(jī)制(在我們使用 IDEA 的時候,如果沒有關(guān)閉相應(yīng)的代碼 Warning 機(jī)制,會發(fā)現(xiàn)在成員變量上的@Autoware是黃色的,也就是 idea 不建議的代碼)。Spring 更推薦構(gòu)造函數(shù)注入的方式:

          @Component
          @Slf4j
          public?class?ConstructorBean?{

          ????private?final?Environment?environment;

          ????@Autowired
          ????public?LogicInConstructorExampleBean(Environment?environment)?{
          ????????this.environment?=?environment;
          ????????log.info(Arrays.asList(environment.getDefaultProfiles()));
          ????}
          }?

          CommandLineRunner

          如果我們的項目使用的是 Spring Boot,那么可以使用 Spring Boot 提供的 CommandLineRunner 接口來實現(xiàn)初始化邏輯,Spring Boot 將在啟動初始化完成之后調(diào)用實現(xiàn)了CommandLineRunner的接口的run方法:

          @Component
          @Slf4j
          public?class?CommandLineAppStartupRunner?implements?CommandLineRunner?{

          ????@Override
          ????public?void?run(String...args)?throws?Exception?{
          ????????log.info("Increment?counter");
          ????}
          }

          并且,多個CommandLineRunner實現(xiàn),可以通過@Order來控制它們的執(zhí)行順序。

          SmartLifecycle

          還有一種更高級的方法來實現(xiàn)我們的邏輯。這可以 Spring 高級開發(fā)必備技能哦。SmartLifecycle 不僅僅能在初始化后執(zhí)行一個邏輯,還能再關(guān)閉前執(zhí)行一個邏輯,并且也可以控制多個 SmartLifecycle 的執(zhí)行順序,就像這個類名表示的一樣,這是一個智能的生命周期管理接口。

          • start():bean 初始化完畢后,該方法會被執(zhí)行。
          • stop():容器關(guān)閉后,spring 容器發(fā)現(xiàn)當(dāng)前對象實現(xiàn)了 SmartLifecycle,就調(diào)用 stop(Runnable), 如果只是實現(xiàn)了 Lifecycle,就調(diào)用 stop()。
          • isRunning:當(dāng)前狀態(tài),用來判你的斷組件是否在運行。
          • getPhase:控制多個 SmartLifecycle 的回調(diào)順序的,返回值越小越靠前執(zhí)行 start() 方法,越靠后執(zhí)行 stop() 方法。
          • isAutoStartup():start 方法被執(zhí)行前先看此方法返回值,返回 false 就不執(zhí)行 start 方法了。
          • stop(Runnable):容器關(guān)閉后,spring 容器發(fā)現(xiàn)當(dāng)前對象實現(xiàn)了 SmartLifecycle,就調(diào)用 stop(Runnable), 如果只是實現(xiàn)了 Lifecycle,就調(diào)用 stop()。
          @Component
          public?class?SmartLifecycleExample?implements?SmartLifecycle?{

          ????private?boolean?isRunning?=?false;

          ????@Override
          ????public?void?start()?{
          ????????System.out.println("start");
          ????????isRunning?=?true;
          ????}

          ????@Override
          ????public?int?getPhase()?{
          ????????//?默認(rèn)為?0
          ????????return?0;
          ????}

          ????@Override
          ????public?boolean?isAutoStartup()?{
          ????????//?默認(rèn)為?false
          ????????return?true;
          ????}

          ????@Override
          ????public?boolean?isRunning()?{
          ????????//?默認(rèn)返回?false
          ????????return?isRunning;
          ????}

          ????@Override
          ????public?void?stop(Runnable?callback)?{
          ????????System.out.println("stop(Runnable)");
          ????????callback.run();
          ????????isRunning?=?false;
          ????}

          ????@Override
          ????public?void?stop()?{
          ????????System.out.println("stop");

          ????????isRunning?=?false;
          ????}

          }


          最近面試BAT,整理一份面試資料Java面試BAT通關(guān)手冊,覆蓋了Java核心技術(shù)、JVM、Java并發(fā)、SSM、微服務(wù)、數(shù)據(jù)庫、數(shù)據(jù)結(jié)構(gòu)等等。

          獲取方式:點“在看”,關(guān)注公眾號并回復(fù)?666?領(lǐng)取,更多內(nèi)容陸續(xù)奉上。

          明天見(??ω??)??
          瀏覽 56
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

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

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  69视频在线免费观看 | 成人片AV免费看FreeSex | 一级无码爱爱片免费 | 探花熟女| 操操操操逼操操操操 |