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

throwable發表於2020-07-14

前提

這篇文章是《SpringBoot2.x入門》專輯的第6篇文章,使用的SpringBoot版本為2.3.1.RELEASEJDK版本為1.8

這篇文章主要簡單聊聊鉤子介面CommandLineRunnerApplicationRunner,下文有時候統稱兩者為Runner

Runner的回撥時機

參考org.springframework.boot.SpringApplication#run()方法的原始碼,可以知道CommandLineRunnerApplicationRunner的回撥時機:

在所有的CommandLineRunnerApplicationRunner回撥之前,下面的步驟已經確保執行完畢:

  1. Environment內建變數的建立和屬性填充已經完成。
  2. Banner已經列印完畢。
  3. ApplicationContextBeanFactory建立完成,並且完成了上下文重新整理(refreshContext),意味著所有單例的Bean完成了初始化以及屬性裝配。
  4. Servlet容器啟動成功,如內建的TomcatJetty容器已經正常啟動,可以正常接收請求和處理。
  5. 啟動資訊完成列印,一般會看到日誌輸出類似Started OrderExportApplication in XXX seconds (JVM running for YYY)

也就是CommandLineRunner或者ApplicationRunner回撥的時候,可以使用所有上下文中存在的單例BeanEnvironment內建變數中已經存在的屬性值,所以很多時候demo專案都會在CommandLineRunner或者ApplicationRunner中進行操作。

Runner的簡單使用

CommandLineRunnerApplicationRunner沒有本質區別,唯一的區別在: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),不定期推送筆者原創技術文章(絕不抄襲或者轉載):

相關文章