1. 程式人生 > >SpringBoot2.x入門:使用CommandLineRunner鉤子介面

SpringBoot2.x入門:使用CommandLineRunner鉤子介面

## 前提 這篇文章是《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