溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

SpringBoot留給開發(fā)者的7個(gè)啟動(dòng)擴(kuò)展點(diǎn)分別是怎樣的

發(fā)布時(shí)間:2021-09-29 15:20:53 來源:億速云 閱讀:151 作者:柒染 欄目:編程語言

這篇文章將為大家詳細(xì)講解有關(guān)SpringBoot留給開發(fā)者的7個(gè)啟動(dòng)擴(kuò)展點(diǎn)分別是怎樣的,文章內(nèi)容質(zhì)量較高,因此小編分享給大家做個(gè)參考,希望大家閱讀完這篇文章后對相關(guān)知識有一定的了解。

我們經(jīng)常需要在容器啟動(dòng)的時(shí)候做一些鉤子動(dòng)作,比如注冊消息消費(fèi)者,監(jiān)聽配置等,今天就總結(jié)下SpringBoot留給開發(fā)者的7個(gè)啟動(dòng)擴(kuò)展點(diǎn)。

容器刷新完成擴(kuò)展點(diǎn)

1、監(jiān)聽容器刷新完成擴(kuò)展點(diǎn)ApplicationListener<ContextRefreshedEvent>

基本用法

熟悉Spring的同學(xué)一定知道,容器刷新成功意味著所有的Bean初始化已經(jīng)完成,當(dāng)容器刷新之后Spring將會(huì)調(diào)用容器內(nèi)所有實(shí)現(xiàn)了ApplicationListener<ContextRefreshedEvent>的Bean的onApplicationEvent方法,應(yīng)用程序可以以此達(dá)到監(jiān)聽容器初始化完成事件的目的。

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

易錯(cuò)的點(diǎn)

這個(gè)擴(kuò)展點(diǎn)用在web容器中的時(shí)候需要額外注意,在web 項(xiàng)目中(例如spring mvc),系統(tǒng)會(huì)存在兩個(gè)容器,一個(gè)是root application context,另一個(gè)就是我們自己的context(作為root application context的子容器)。如果按照上面這種寫法,就會(huì)造成onApplicationEvent方法被執(zhí)行兩次。解決此問題的方法如下:

@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) {          if (event.getApplicationContext().getParent() == null) {              // root application context 沒有parent              LOG.info("Increment counter");              counter++;          }      }  }

高階玩法

當(dāng)然這個(gè)擴(kuò)展還可以有更高階的玩法:自定義事件,可以借助Spring以最小成本實(shí)現(xiàn)一個(gè)觀察者模式:

  •  先自定義一個(gè)事件: 

public class NotifyEvent extends ApplicationEvent {      private String email;      private String content;      public NotifyEvent(Object source) {          super(source);      }      public NotifyEvent(Object source, String email, String content) {          super(source);          this.email = email;          this.content = content;      }      // 省略getter/setter方法  }
  •  注冊一個(gè)事件監(jiān)聽器 

@Component  public class NotifyListener implements ApplicationListener<NotifyEvent> {      @Override      public void onApplicationEvent(NotifyEvent event) {          System.out.println("郵件地址:" + event.getEmail());          System.out.println("郵件內(nèi)容:" + event.getContent());      }  }
  •  發(fā)布事件 

@RunWith(SpringRunner.class)  @SpringBootTest  public class ListenerTest {      @Autowired      private WebApplicationContext webApplicationContext;      @Test      public void testListener() {          NotifyEvent event = new NotifyEvent("object", "abc@qq.com", "This is the content");          webApplicationContext.publishEvent(event);      }  }
  •  執(zhí)行單元測試可以看到郵件的地址和內(nèi)容都被打印出來了

2、SpringBoot的CommandLineRunner接口

當(dāng)容器上下文初始化完成之后,SpringBoot也會(huì)調(diào)用所有實(shí)現(xiàn)了CommandLineRunner接口的run方法,下面這段代碼可起到和上文同樣的作用:

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

對于這個(gè)擴(kuò)展點(diǎn)的使用有額外兩點(diǎn)需要注意:

  • 多個(gè)實(shí)現(xiàn)了CommandLineRunner的Bean的執(zhí)行順序可以根據(jù)Bean上的@Order注解調(diào)整

  • 其run方法可以接受從控制臺輸入的參數(shù),跟ApplicationListener<ContextRefreshedEvent>這種擴(kuò)展相比,更加靈活 

// 從控制臺輸入?yún)?shù)示例  java -jar CommandLineAppStartupRunner.jar abc abcd

3、SpringBoot的ApplicationRunner接口

這個(gè)擴(kuò)展和SpringBoot的CommandLineRunner接口的擴(kuò)展類似,只不過接受的參數(shù)是一個(gè)ApplicationArguments類,對控制臺輸入的參數(shù)提供了更好的封裝,以--開頭的被視為帶選項(xiàng)的參數(shù),否則是普通的參數(shù)

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

比如:

java -jar CommandLineAppStartupRunner.jar abc abcd --autho=mark verbose

Bean初始化完成擴(kuò)展點(diǎn)

前面的內(nèi)容總結(jié)了針對容器初始化的擴(kuò)展點(diǎn),在有些場景,比如監(jiān)聽消息的時(shí)候,我們希望Bean初始化完成之后立刻注冊監(jiān)聽器,而不是等到整個(gè)容器刷新完成,Spring針對這種場景同樣留足了擴(kuò)展點(diǎn):

1、@PostConstruct注解 

@PostConstruct注解一般放在Bean的方法上,被@PostConstruct修飾的方法會(huì)在Bean初始化后馬上調(diào)用:  @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()));      }  }

2、 InitializingBean接口

InitializingBean的用法基本上與@PostConstruct一致,只不過相應(yīng)的Bean需要實(shí)現(xiàn)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()));      }  }

3、@Bean注解的初始化方法

通過@Bean注入Bean的時(shí)候可以指定初始化方法:

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

4、通過構(gòu)造函數(shù)注入

Spring也支持通過構(gòu)造函數(shù)注入,我們可以把搞事情的代碼寫在構(gòu)造函數(shù)中,同樣能達(dá)到目的

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

Bean初始化完成擴(kuò)展點(diǎn)執(zhí)行順序?

可以用一個(gè)簡單的測試:

@Component  @Scope(value = "prototype")  public class AllStrategiesExampleBean implements InitializingBean {      private static final Logger LOG         = Logger.getLogger(AllStrategiesExampleBean.class);      public AllStrategiesExampleBean() {          LOG.info("Constructor");      }      @Override      public void afterPropertiesSet() throws Exception {          LOG.info("InitializingBean");      }      @PostConstruct      public void postConstruct() {         LOG.info("PostConstruct");      }      public void init() {          LOG.info("init-method");      }  }

實(shí)例化這個(gè)Bean后輸出:

[main] INFO o.b.startup.AllStrategiesExampleBean - Constructor  [main] INFO o.b.startup.AllStrategiesExampleBean - PostConstruct  [main] INFO o.b.startup.AllStrategiesExampleBean - InitializingBean  [main] INFO o.b.startup.AllStrategiesExampleBean - init-method

關(guān)于SpringBoot留給開發(fā)者的7個(gè)啟動(dòng)擴(kuò)展點(diǎn)分別是怎樣的就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,可以學(xué)到更多知識。如果覺得文章不錯(cuò),可以把它分享出去讓更多的人看到。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI