[TOC]
我們經常需要在容器啟動的時候做一些鉤子動作,比如注冊消息消費者,監聽配置等,今天就總結下`SpringBoot`留給開發者的7個啟動擴展點。
## 容器刷新完成擴展點
### 1、監聽容器刷新完成擴展點`ApplicationListener<ContextRefreshedEvent>`
#### 基本用法
熟悉`Spring`的同學一定知道,容器刷新成功意味著所有的`Bean`初始化已經完成,當容器刷新之后`Spring`將會調用容器內所有實現了`ApplicationListener<ContextRefreshedEvent>`的`Bean`的`onApplicationEvent`方法,應用程序可以以此達到監聽容器初始化完成事件的目的。
```java
@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++;
}
}
```
#### 易錯的點
這個擴展點用在`web`容器中的時候需要額外注意,在web 項目中(例如`spring mvc`),系統會存在兩個容器,一個是`root application context`,另一個就是我們自己的`context`(作為`root application context`的子容器)。如果按照上面這種寫法,就會造成`onApplicationEvent`方法被執行兩次。解決此問題的方法如下:
~~~go
@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++;
}
}
}
~~~
#### 高階玩法
當然這個擴展還可以有更高階的玩法:**自定義事件**,可以借助`Spring`以最小成本實現一個觀察者模式:
* 先自定義一個事件:
~~~go
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方法
}
~~~
* 注冊一個事件監聽器
~~~go
@Component
public class NotifyListener implements ApplicationListener<NotifyEvent> {
@Override
public void onApplicationEvent(NotifyEvent event) {
System.out.println("郵件地址:" + event.getEmail());
System.out.println("郵件內容:" + event.getContent());
}
}
~~~
* 發布事件
~~~go
@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);
}
}
~~~
* 執行單元測試可以看到郵件的地址和內容都被打印出來了
### 2、`SpringBoot`的`CommandLineRunner`接口
當容器上下文初始化完成之后,`SpringBoot`也會調用所有實現了`CommandLineRunner`接口的`run`方法,下面這段代碼可起到和上文同樣的作用:
~~~go
@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++;
}
}
~~~
對于這個擴展點的使用有額外兩點需要注意:
* 多個實現了`CommandLineRunner`的`Bean`的執行順序可以根據`Bean`上的`@Order`注解調整
* 其`run`方法可以接受從控制臺輸入的參數,跟`ApplicationListener<ContextRefreshedEvent>`這種擴展相比,更加靈活
~~~go
// 從控制臺輸入參數示例
java -jar CommandLineAppStartupRunner.jar abc abcd
~~~
### 3、`SpringBoot`的`ApplicationRunner`接口
這個擴展和`SpringBoot`的`CommandLineRunner`接口的擴展類似,只不過接受的參數是一個`ApplicationArguments`類,對控制臺輸入的參數提供了更好的封裝,以`--`開頭的被視為帶選項的參數,否則是普通的參數
~~~go
@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++;
}
}
~~~
比如:
~~~go
java?-jar?CommandLineAppStartupRunner.jar?abc?abcd?--autho=mark?verbose
~~~
## 二、Bean初始化完成擴展點
前面的內容總結了針對容器初始化的擴展點,在有些場景,比如監聽消息的時候,我們希望`Bean`初始化完成之后立刻注冊監聽器,而不是等到整個容器刷新完成,`Spring`針對這種場景同樣留足了擴展點:
### 1、`@PostConstruct`注解
`@PostConstruct`注解一般放在`Bean`的方法上,被`@PostConstruct`修飾的方法會在`Bean`初始化后馬上調用:
~~~go
@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`一致,只不過相應的`Bean`需要實現`afterPropertiesSet`方法
~~~go
@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`的時候可以指定初始化方法:
**`Bean`的定義**
~~~go
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`注入**
~~~go
@Bean(initMethod="init")
public InitMethodExampleBean initMethodExampleBean() {
return new InitMethodExampleBean();
}
~~~
### 4、通過構造函數注入
`Spring`也支持通過構造函數注入,我們可以把搞事情的代碼寫在構造函數中,同樣能達到目的
~~~go
@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初始化完成擴展點執行順序?
可以用一個簡單的測試:
~~~go
@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");
}
}
~~~
實例化這個`Bean`后輸出:
~~~go
[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
~~~
> 文章轉載自:https://blog.csdn.net/weixin_36380516/article/details/115388363
- 簡介
- 更新說明
- 其他作品
- 第一部分 Java框架基礎
- 第一章 Java基礎
- 多線程實戰
- 嘗試一下Guava帶返回值的多線程處理類ListenableFuture
- LocalDate和Date有什么區別
- JAVA8接口增強實踐
- 第二章 Spring框架基礎
- MVC究竟是個啥?
- @ApiImplicitParam
- 七種方式,教你在SpringBoot初始化時搞點事情!
- Spring事務狀態
- maven
- Mybatis小總結
- mybatis-plus的使用
- 第三章 SpringSecurity實戰
- 基于SpringSecurity+jwt的用戶認證
- spring-security-oauth2
- 第四章 數據庫
- mysql
- mysql授權
- mysql數據庫三個關鍵性能指標--TPS\QPS\IOPS
- 梳理一下那些年Mysql的弱語法可能會踩的坑
- 關于Mysql的“字符串”數值的轉換和使用
- 憑這一文咱把事務講透
- Mysql性能優化
- 查詢性能優化
- 不常用的一些語法
- elasticsearch
- elasticsearch文檔操作
- 索引的基本操作
- java操作ElaticSearch
- elasticsearch中的各種查詢
- DB與ES混合應用可能存在的問題及解決方案探索
- 使用es必須要知道的一些知識點:索引篇
- Es中的日期操作
- MongoDB
- 入門篇(了解非關系型數據庫 NoSQL - MongoDB)
- 集群分片 (高級篇)
- 互聯網大廠的建表規范
- 第五章 中間件
- nginx
- nginx動靜分離配置,這個雷你踩過嗎?
- Canal
- Sharding-jdbc
- 水平分庫實踐
- kafka
- 第六章 版本管理
- git
- Not currently on any branch 情況提交版本
- 第七章 IO編程
- 第八章 JVM實戰調優
- jvisualvm
- jstat
- 第二部分 高級項目實戰篇
- 第一章 微信開發實戰
- 第二章 文件處理
- 使用EasyExcel處理導入導出
- 第三章 踩坑指南
- 郵件發送功能
- 第三部分 架構實戰篇
- 第一章 架構實戰原則
- 接口防止重復調用的一種方案
- 第二章 高并發緩存一致性管理辦法
- 第三章 異地多活場景下的數據同步之道
- 第四章 用戶體系
- 集成登錄
- auth-sso的管理
- 第五章 分庫分表場景
- 第六章 秒殺與高并發
- 秒殺場景
- 第七章 業務中臺
- 中臺的使用效果是怎樣的?
- 通用黑白名單方案
- 第八章 領域驅動設計
- 第十一章 微服務實戰
- Nacos多環境管理之道
- logback日志雙寫問題及Springboot項目正確的啟動方式
- 第四部分 優雅的代碼
- java中的鏈式編程
- 面向對象
- 開發原則
- Stream操作案例分享
- 注重性能的代碼
- 第五部分 談談成長
- 新手入門指北
- 不可不知的調試技巧
- 構建自己的知識體系
- 我是如何做筆記的
- 有效的提問
- 謹防思維定勢
- 學會與上級溝通
- 想清楚再去做
- 碎片化學習
- 第六部分 思維導圖(付費)
- 技術基礎篇
- 技術框架篇
- 數據存儲篇
- 項目實戰篇
- 第七部分 吾愛開源
- 7-1 麻雀聊天
- 項目啟動
- 前端登錄無請求問題解決
- websocket測試
- 7-2 ocp微服務框架
- evm框架集成
- 項目構建與集成
- zentao-center
- 二次開發:初始框架的搭建
- 二次開發:增加細分菜單、權限到應用
- 7-3 書棧網
- 項目啟動
- 源碼分析
- 我的書架
- 文章發布機制
- IM
- 第八章 團隊管理篇
- 大廠是怎么運作的
- 第九章 碼山有道
- 簡歷內推
- 聯系我內推
- 第十章 學點前端
- Vue