前提
這篇文章是《SpringBoot2.x入門》專輯的第6篇文章,使用的SpringBoot
版本為2.3.1.RELEASE
,JDK
版本為1.8
。
這篇文章主要簡單聊聊鉤子介面CommandLineRunner
和ApplicationRunner
,下文有時候統稱兩者為Runner
。
Runner的回撥時機
參考org.springframework.boot.SpringApplication#run()
方法的原始碼,可以知道CommandLineRunner
和ApplicationRunner
的回撥時機:
在所有的CommandLineRunner
和ApplicationRunner
回撥之前,下面的步驟已經確保執行完畢:
Environment
內建變數的建立和屬性填充已經完成。Banner
已經列印完畢。ApplicationContext
和BeanFactory
建立完成,並且完成了上下文重新整理(refreshContext
),意味著所有單例的Bean
完成了初始化以及屬性裝配。Servlet
容器啟動成功,如內建的Tomcat
、Jetty
容器已經正常啟動,可以正常接收請求和處理。- 啟動資訊完成列印,一般會看到日誌輸出類似
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
上下文中。例如:
@Slf4j
@Component
public class CustomCommandLineRunner implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
log.info("CustomCommandLineRunner runs...");
}
}
也可以通過@Bean
註解,直接作用於CommandLineRunner
的匿名類對應的方法上,例如:
@Slf4j
@Configuration
public class CommandLineRunners {
@Bean
public CommandLineRunner commandLineRunner(){
return args -> log.info("CommandLineRunners commandLineRunner");
}
}
或者直接在啟動類實現CommandLineRunner
介面(這種方式不推薦使用):
@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
),為了簡單起見排程器使用記憶體態:
@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);
}
}
啟動應用後,日誌如下:
小結
本文demo
專案倉庫:
(本文完 c-2-d e-a-20200712)
技術公眾號《Throwable文摘》(id:throwable-doge),不定期推送筆者原創技術文章(絕不抄襲或者轉載):