SpringBoot2.x入門:使用CommandLineRunner鉤子介面
阿新 • • 發佈:2020-07-14
## 前提
這篇文章是《SpringBoot2.x入門》專輯的**第6篇**文章,使用的`SpringBoot`版本為`2.3.1.RELEASE`,`JDK`版本為`1.8`。
這篇文章主要簡單聊聊鉤子介面`CommandLineRunner`和`ApplicationRunner`,下文有時候統稱兩者為`Runner`。
## Runner的回撥時機
參考`org.springframework.boot.SpringApplication#run()`方法的原始碼,可以知道`CommandLineRunner`和`ApplicationRunner`的回撥時機:
![](https://throwable-blog-1256189093.cos.ap-guangzhou.myqcloud.com/202007/s-p-g-ch6-1.png)
在所有的`CommandLineRunner`和`ApplicationRunner`回撥之前,下面的步驟已經確保執行完畢:
1. `Environment`內建變數的建立和屬性填充已經完成。
2. `Banner`已經列印完畢。
3. `ApplicationContext`和`BeanFactory`建立完成,並且完成了上下文重新整理(`refreshContext`),意味著所有單例的`Bean`完成了初始化以及屬性裝配。
4. `Servlet`容器啟動成功,如內建的`Tomcat`、`Jetty`容器已經正常啟動,可以正常接收請求和處理。
5. 啟動資訊完成列印,一般會看到日誌輸出類似`Started OrderExportApplication in XXX seconds (JVM running for YYY)`。
也就是`CommandLineRunner`或者`ApplicationRunner`回撥的時候,可以使用所有上下文中存在的單例`Bean`和`Environment`內建變數中已經存在的屬性值,所以很多時候`demo`專案都會在`CommandLineRunner`或者`ApplicationRunner`中進行操作。
## Runner的簡單使用
`CommandLineRunner`和`ApplicationRunner`沒有本質區別,唯一的區別在:`CommandLineRunner#run()`接收來自於`main`方法的引數,型別是字串陣列(不定字串陣列),而`ApplicationRunner#run()`接收`ApplicationArguments`型別的引數,對應的實現類是`DefaultApplicationArguments`。
可以直接把註解`@Component`應用在`CommandLineRunner`或者`ApplicationRunner`的實現類上,相對於把對應的實現單例新增到`Spring`上下文中。例如:
```java
@Slf4j
@Component
public class CustomCommandLineRunner implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
log.info("CustomCommandLineRunner runs...");
}
}
```
也可以通過`@Bean`註解,直接作用於`CommandLineRunner`的匿名類對應的方法上,例如:
```java
@Slf4j
@Configuration
public class CommandLineRunners {
@Bean
public CommandLineRunner commandLineRunner(){
return args -> log.info("CommandLineRunners commandLineRunner");
}
}
```
或者直接在啟動類實現`CommandLineRunner`介面(**這種方式不推薦使用**):
```java
@Slf4j
@SpringBootApplication
public class Ch5Application implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(Ch5Application.class, args);
}
@Override
public void run(String... args) throws Exception {
log.info("Ch5Application CommandLineRunner runs...");
}
}
```
此外,可以通過實現`org.springframework.core.Ordered`介面或者`@Order`註解定義`Runner`回撥的順序,指定的順序數越小,優先順序越高。
## Runner的使用場景
這一小節是根據個人的程式設計習慣提出的建議。`Runner`鉤子介面回撥的時候**如果丟擲異常,會直接導致應用程序退出**,所以如果在`Runner`回撥方法中一定要注意異常的捕獲和處理。基於這個特性,結合前面分析`Runner`介面的回撥時機,它適用的主要場景有:
- 列印日誌用於標識服務啟動成功或者標識某些屬性載入成功。
- 設定屬性值或者啟動元件,例如開啟某些元件的開關、一些應用級別快取的載入、啟動定時任務等等。
- 預載入資料(更常見於一些測試場景中,可以結合`@Profile`註解使用,指定特定的`profile`才生效)。
- 需要使用`main`方法的入參。
下面使用`CommandLineRunner`啟動所有`Quartz`中的`Job`(記得先引入依賴`spring-boot-starter-quartz`以及`quartz`),為了簡單起見排程器使用記憶體態:
```java
@Slf4j
@DisallowConcurrentExecution
public class SimpleJob extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
log.info("SimpleJob run...");
}
}
@Component
public class QuartzCommandLineRunner implements CommandLineRunner {
@Autowired
private Scheduler scheduler;
@Override
public void run(String... args) throws Exception {
JobDetail job = JobBuilder.newJob(SimpleJob.class).storeDurably().withIdentity(JobKey.jobKey("SimpleJob")).build();
// 30秒執行一次
Trigger trigger = TriggerBuilder.newTrigger()
.withSchedule(SimpleScheduleBuilder.simpleSchedule().repeatForever().withIntervalInSeconds(30))
.forJob(job).build();
scheduler.scheduleJob(job, trigger);
}
}
```
啟動應用後,日誌如下:
![](https://throwable-blog-1256189093.cos.ap-guangzhou.myqcloud.com/202007/s-p-g-ch6-2.png)
## 小結
本文`demo`專案倉庫:
- `Github`:https://github.com/zjcscut/spring-boot-guide/tree/master/ch5-runner
(本文完 c-2-d e-a-20200712)
技術公眾號《Throwable文摘》(id:throwable-doge),不定期推送筆者原創技術文章(絕不抄襲或者轉載):
![](https://public-1256189093.cos.ap-guangzhou.myqcloud.com/static/wechat-account-lo