偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

一文帶你掌握ApplicationRunner和CommandLineRunner如何使用及實(shí)現(xiàn)原理

開(kāi)發(fā) 前端
CommandLineRunner? 和 ApplicationRunner? 常用于應(yīng)用啟動(dòng)后的初始化任務(wù)或一次性任務(wù)執(zhí)行。它們?cè)试S你在 Spring 應(yīng)用啟動(dòng)完成后立即執(zhí)行一些邏輯。

1.概述

ApplicationRunner 和 CommandLineRunner 是 Spring Boot 提供的兩個(gè)接口,允許在 Spring 應(yīng)用程序啟動(dòng)完成后執(zhí)行特定的代碼。它們的主要作用是在應(yīng)用啟動(dòng)后執(zhí)行一段初始化或任務(wù)邏輯,常見(jiàn)于一些啟動(dòng)任務(wù),例如加載數(shù)據(jù)、驗(yàn)證配置、執(zhí)行調(diào)度等。接下來(lái)我們就來(lái)詳細(xì)看看它們?cè)陧?xiàng)目開(kāi)發(fā)中的實(shí)際應(yīng)用

2.實(shí)際應(yīng)用

這兩個(gè)擴(kuò)展點(diǎn)在實(shí)際開(kāi)發(fā)中的應(yīng)用場(chǎng)景挺廣泛的,下面就來(lái)看看幾個(gè)常見(jiàn)的。

2.1 服務(wù)啟動(dòng)后數(shù)據(jù)初始化

在應(yīng)用啟動(dòng)時(shí)加載初始化數(shù)據(jù),如將初始數(shù)據(jù)加載到數(shù)據(jù)庫(kù)、從文件讀取數(shù)據(jù)、緩存熱點(diǎn)數(shù)據(jù)等??梢栽?CommandLineRunner 或 ApplicationRunner 中執(zhí)行這些初始化邏輯,確保在應(yīng)用服務(wù)正式對(duì)外提供服務(wù)之前,必要的數(shù)據(jù)已經(jīng)準(zhǔn)備好。

這個(gè)應(yīng)用場(chǎng)景我深有感觸,因?yàn)檫@種應(yīng)用操作可以“去運(yùn)維化”,尤其對(duì)于系統(tǒng)是新安裝或部署而言,它確保應(yīng)用程序所需的上下文數(shù)據(jù)無(wú)誤,可以立即開(kāi)始運(yùn)行,而無(wú)需通過(guò)手動(dòng)干預(yù)來(lái)插入基本數(shù)據(jù)(PS:環(huán)境基礎(chǔ)數(shù)據(jù)靠運(yùn)維部署時(shí)候去插入總是不可靠的......)。本著天助自助者的原則,我們可以通過(guò)CommandLineRunner來(lái)完成項(xiàng)目服務(wù)環(huán)境初始化的工作,這里以平時(shí)的后臺(tái)管理系統(tǒng)來(lái)講述一下,大部分的后臺(tái)系統(tǒng)都是基于RBAC模型(Role-Based Access Control:基于角色的訪問(wèn)控制)進(jìn)行授權(quán)和認(rèn)證的,這就意味著我們一個(gè)全新系統(tǒng)部署之后,會(huì)默認(rèn)插入一個(gè)超管賬號(hào),他可以登陸系統(tǒng)訪問(wèn)所有功能,比如說(shuō)他可以新增員工,給新員工賦予權(quán)限等等,這樣系統(tǒng)就可以用起來(lái)了。

@Component
public class DataInitializer implements CommandLineRunner {
    @Resource
    private EnvInitMapper envInitMapper;
    @Resource
    private UserService userService;
    @Resource
    private RoleService roleService;
    @Resource
    private UserRoleService userRoleService;
    
    @Override
    public void run(String... args) throws Exception {
        // 1/判斷是不是第一次啟動(dòng) 若是,執(zhí)行初始數(shù)據(jù)插入等操作 若不是,不執(zhí)行
        // 這個(gè)可以讀取數(shù)據(jù)庫(kù)標(biāo)志,初始化之后插入一個(gè)標(biāo)志記錄即可, 當(dāng)然也可以讀取緩存
        QueryWrapper<EnvInit>queryWrapper = new QueryWrapper<>();
        EnvInit init = envInitMapper.selectOne(queryWrapper);
        if (Objects.isNull(init)) {
            // 2.第一次初始化環(huán)境
            userService.firstInitData();
            // 3.插入已經(jīng)初始化標(biāo)志
            init = new EnvInit();
            init.setIsInit(1);
            envInitMapper.insert(init);
        }
    }

    /**
     * 初始化環(huán)境基礎(chǔ)數(shù)據(jù),可以插入環(huán)境所需的任何數(shù)據(jù)
     */
    @Transactional(rollbackFor = Exception.class)
    public void initData() {
        userService.firstInitData();
        roleService.firstInitData();
        userRoleService.firstInitData();
    }
}

這里我們只是舉例插入了菜單權(quán)限所需的基礎(chǔ)數(shù)據(jù),你可以根據(jù)自身服務(wù)環(huán)境需求插入所需的任何基礎(chǔ)數(shù)據(jù),以保證系統(tǒng)能夠順利正常運(yùn)行。我們還判斷了是不是第一次初始化基礎(chǔ)數(shù)據(jù),防止每次系統(tǒng)服務(wù)重啟之后重復(fù)插入。

2.2 應(yīng)用啟動(dòng)時(shí)加載配置信息

在某些情況下,應(yīng)用可能需要在啟動(dòng)時(shí)加載外部配置信息或數(shù)據(jù)庫(kù)中的參數(shù)到內(nèi)存中進(jìn)行緩存。

@Component
public class ConfigInitializer implements CommandLineRunner {

    @Override
    public void run(String... args) {
        // 加載配置文件或數(shù)據(jù)庫(kù)配置信息
        System.out.println("加載配置信息...");
        // 例如加載外部配置文件
        // Config config = configService.loadConfig();
    }
}

2.3 啟動(dòng)時(shí)加載數(shù)據(jù)到緩存

有時(shí)你可能希望在應(yīng)用啟動(dòng)時(shí)將一些常用數(shù)據(jù)(如字典數(shù)據(jù)、熱點(diǎn)數(shù)據(jù))加載到內(nèi)存中,以提高訪問(wèn)效率。

@Component
public class DataCacheInitializer implements ApplicationRunner {

    @Override
    public void run(ApplicationArguments args) {
        System.out.println("啟動(dòng)時(shí)加載字典數(shù)據(jù)到緩存...");
        // 假設(shè)從數(shù)據(jù)庫(kù)加載數(shù)據(jù)到緩存
        // List<Dict> dicts = dictService.loadAll();
        // cacheService.cacheDicts(dicts);
    }
}

2.4 啟動(dòng)時(shí)驗(yàn)證環(huán)境配置

之前我們總結(jié)過(guò):license版權(quán)證書(shū)生成與驗(yàn)證,對(duì)license不太熟悉的可自行跳轉(zhuǎn)查看,簡(jiǎn)單概括來(lái)說(shuō)就是,你是軟件服務(wù)商,人家買(mǎi)了你的軟件,要求你部署到他們的服務(wù)器上,即本地化部署,這時(shí)候你就需要打成JAR包去客戶(hù)服務(wù)器上部署,如果就是簡(jiǎn)單的java -jar jar包服務(wù)就能啟動(dòng)跑起來(lái)了,那客戶(hù)就可以拿著你的jar包去賣(mài)了.....license就是為了堵住這個(gè)缺口,加了證書(shū)驗(yàn)證,讓你換個(gè)環(huán)境跑不起來(lái)......

@Component
public class LicenseCheckApplicationRunner implements ApplicationRunner {
    @Resource
    private LicenseVerify licenseVerify;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        LicenseContent content = licenseVerify.install();
    }
}
@Component
public class LicenseVerify {
    @Resource
    private LicenseProperties licenseProperties;

    private static Logger logger = LogManager.getLogger(LicenseVerify.class);
    private static final  DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");


    /**
     * 安裝License證書(shū)
     * 項(xiàng)目服務(wù)啟動(dòng)時(shí)候安裝證書(shū),檢驗(yàn)合法性
     * 此時(shí)根據(jù)開(kāi)關(guān)驗(yàn)證服務(wù)器系統(tǒng)信息
     */
    public synchronized LicenseContent install() {
        LicenseContent result = null;
        try{
            LicenseManager licenseManager = new LicenseManager(initLicenseParam());
            licenseManager.uninstall();
            result = licenseManager.install(new File(licenseProperties.getLicensePath()));
            verifySystemInfo(result);
            logger.info("證書(shū)安裝成功,證書(shū)有效期:{} - {}", df.format(result.getNotBefore()),
                    df.format(result.getNotAfter()));
        }catch (Exception e){
            logger.error("證書(shū)安裝失敗:", e);
            throw new BizException("證書(shū)安裝失敗");
        }
        return result;
    }

    /**
     * 校驗(yàn)License證書(shū), 在接口使用{@link com.plasticene.boot.license.core.anno.License}
     * 時(shí)候進(jìn)入license切面時(shí)候調(diào)用,此時(shí)無(wú)需再驗(yàn)證服務(wù)器系統(tǒng)信息,驗(yàn)證證書(shū)和有效期即可
     */
    public boolean verify() {
        try {
            LicenseManager licenseManager = new LicenseManager(initLicenseParam());
            LicenseContent licenseContent = licenseManager.verify();
            verifyExpiry(licenseContent);
            return true;
        }catch (Exception e){
            logger.error("證書(shū)校驗(yàn)失敗:", e);
            throw new BizException("證書(shū)檢驗(yàn)失敗");
        }
    }

    /**
     * 初始化證書(shū)生成參數(shù)
     */
    private LicenseParam initLicenseParam(){
        Preferences preferences = Preferences.userNodeForPackage(LicenseVerify.class);

        CipherParam cipherParam = new DefaultCipherParam(licenseProperties.getStorePass());

        KeyStoreParam publicStoreParam = new CustomKeyStoreParam(LicenseVerify.class
                ,licenseProperties.getPublicKeysStorePath()
                ,licenseProperties.getPublicAlias()
                ,licenseProperties.getStorePass()
                ,null);

        return new DefaultLicenseParam(licenseProperties.getSubject()
                ,preferences
                ,publicStoreParam
                ,cipherParam);
    }

    // 驗(yàn)證證書(shū)有效期
    private void verifyExpiry(LicenseContent licenseContent) {
        Date expiry = licenseContent.getNotAfter();
        Date current = new Date();
        if (current.after(expiry)) {
            throw new BizException("證書(shū)已過(guò)期");
        }
    }

    private void verifySystemInfo(LicenseContent licenseContent) {
        if (licenseProperties.getVerifySystemSwitch()) {
            SystemInfo systemInfo = (SystemInfo) licenseContent.getExtra();
            VerifySystemType verifySystemType = licenseProperties.getVerifySystemType();
            switch (verifySystemType) {
                case CPU_ID:
                    checkCpuId(systemInfo.getCpuId());
                    break;
                case SYSTEM_UUID:
                    checkSystemUuid(systemInfo.getUuid());
                    break;
                default:
            }
        }
    }


    private void checkCpuId(String cpuId) {
        cpuId = cpuId.trim().toUpperCase();
        String systemCpuId = DmcUtils.getCpuId().trim().toUpperCase();
        logger.info("配置cpuId = {},  系統(tǒng)cpuId = {}", cpuId, systemCpuId);
        if (!Objects.equals(cpuId, systemCpuId)) {
            throw new BizException("license檢驗(yàn)cpuId不一致");
        }
    }

    private void checkSystemUuid(String uuid) {
        uuid = uuid.trim().toUpperCase();
        String systemUuid = DmcUtils.getSystemUuid().trim().toUpperCase();
        logger.info("配置uuid = {},  系統(tǒng)uuid= {}", uuid, systemUuid);
        if (!Objects.equals(uuid, systemUuid)) {
            throw new BizException("license檢驗(yàn)uuid不一致");
        }
    }

}

如果證書(shū)校驗(yàn)不通過(guò),就會(huì)拋出異常,項(xiàng)目服務(wù)啟動(dòng)失敗。

2.5 配合 @Order 使用

在同一個(gè) Spring Boot 應(yīng)用中,可能會(huì)有多個(gè) CommandLineRunner 或 ApplicationRunner 實(shí)現(xiàn)類(lèi)。如果你希望控制它們的執(zhí)行順序,可以使用 @Order 注解,指定多個(gè) Runner 的執(zhí)行順序。

@Component
@Order(1) // 這個(gè)Runner會(huì)優(yōu)先執(zhí)行
public class FirstRunner implements CommandLineRunner {

    @Override
    public void run(String... args) throws Exception {
        System.out.println("FirstRunner running!");
    }
}

@Component
@Order(2) // 這個(gè)Runner會(huì)后執(zhí)行
public class SecondRunner implements CommandLineRunner {

    @Override
    public void run(String... args) throws Exception {
        System.out.println("SecondRunner running!");
    }
}

當(dāng)應(yīng)用啟動(dòng)時(shí),F(xiàn)irstRunner 會(huì)先執(zhí)行,然后 SecondRunner 執(zhí)行。

上面的示例都是使用CommandLineRunner,當(dāng)然換成ApplicationRunner也是可以的。

項(xiàng)目推薦:基于SpringBoot2.x、SpringCloud和SpringCloudAlibaba企業(yè)級(jí)系統(tǒng)架構(gòu)底層框架封裝,解決業(yè)務(wù)開(kāi)發(fā)時(shí)常見(jiàn)的非功能性需求,防止重復(fù)造輪子,方便業(yè)務(wù)快速開(kāi)發(fā)和企業(yè)技術(shù)??蚣芙y(tǒng)一管理。引入組件化的思想實(shí)現(xiàn)高內(nèi)聚低耦合并且高度可配置化,做到可插拔。嚴(yán)格控制包依賴(lài)和統(tǒng)一版本管理,做到最少化依賴(lài)。注重代碼規(guī)范和注釋?zhuān)浅_m合個(gè)人學(xué)習(xí)和企業(yè)使用

Github地址:https://github.com/plasticene/plasticene-boot-starter-parent

Gitee地址:https://gitee.com/plasticene3/plasticene-boot-starter-parent

微信公眾號(hào):Shepherd進(jìn)階筆記

交流探討qun:Shepherd_126

3. CommandLineRunner和ApplicationRunner區(qū)別

直接看定義:

/**
 * Interface used to indicate that a bean should <em>run</em> when it is contained within
 * a {@link SpringApplication}. Multiple {@link CommandLineRunner} beans can be defined
 * within the same application context and can be ordered using the {@link Ordered}
 * interface or {@link Order @Order} annotation.
 * <p>
 * If you need access to {@link ApplicationArguments} instead of the raw String array
 * consider using {@link ApplicationRunner}.
 * 如果你需要訪問(wèn)ApplicationArguments去替換掉字符串?dāng)?shù)組,可以考慮使用ApplicationRunner類(lèi)。
 * @author Dave Syer
 * @since 1.0.0
 * @see ApplicationRunner
 */
@FunctionalInterface
public interface CommandLineRunner {

 /**
  * Callback used to run the bean.
  * @param args incoming main method arguments
  * @throws Exception on error
  */
 void run(String... args) throws Exception;

}

ApplicationRunner 和 CommandLineRunner 類(lèi)似,也是一個(gè)在應(yīng)用啟動(dòng)后執(zhí)行的接口。但它更加強(qiáng)大,因?yàn)樗褂昧?ApplicationArguments 對(duì)象,而不僅僅是簡(jiǎn)單的字符串?dāng)?shù)組。ApplicationArguments 允許更方便地處理傳入的參數(shù),例如獲取無(wú)選項(xiàng)參數(shù)和帶選項(xiàng)參數(shù)

@Component
public class MyCommandLineRunner implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        System.out.println("CommandLineRunner running!");
        for (String arg : args) {
            System.out.println("CommandLineRunner Arg: " + arg);
        }
    }
}

@Component
public class MyApplicationRunner implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println("ApplicationRunner running!");
        for (String sourceArg : args.getSourceArgs()) {
            System.out.println("ApplicationRunner Arg: " + sourceArg);
        }
        for (String nonOptionArg : args.getNonOptionArgs()) {
            System.out.println("ApplicationRunner nonOptionArg: " + nonOptionArg);
        }
        for (String optionName : args.getOptionNames()) {
            System.out.println("ApplicationRunner optionArg: " + args.getOptionValues(optionName));
        }


    }
}

在IDEA中配置項(xiàng)目啟動(dòng)參數(shù):

圖片圖片

啟動(dòng)服務(wù),控制臺(tái)輸出如下:

ApplicationRunner running!
ApplicationRunner Arg: hello
ApplicationRunner Arg: 666
ApplicationRunner Arg: --foo=boo
ApplicationRunner Arg: --foo=eoo
ApplicationRunner Arg: world
ApplicationRunner nonOptionArg: hello
ApplicationRunner nonOptionArg: 666
ApplicationRunner nonOptionArg: world
ApplicationRunner optionArg: [boo, eoo]
CommandLineRunner running!
CommandLineRunner Arg: hello
CommandLineRunner Arg: 666
CommandLineRunner Arg: --foo=boo
CommandLineRunner Arg: --foo=eoo
CommandLineRunner Arg: world

區(qū)別如下:

  • 參數(shù)處理:

CommandLineRunner 接收一個(gè) String... args,只是簡(jiǎn)單地傳遞命令行參數(shù)。

ApplicationRunner 使用 ApplicationArguments 對(duì)象,它提供了對(duì)選項(xiàng)和非選項(xiàng)參數(shù)的更強(qiáng)大支持。

  • 用法場(chǎng)景:
  • 如果只是簡(jiǎn)單地處理命令行參數(shù)或執(zhí)行一些任務(wù),CommandLineRunner 足夠。

  • 如果你需要更靈活的方式來(lái)處理命令行選項(xiàng)和參數(shù),ApplicationRunner 更合適。

  • 參數(shù)管理:

  • CommandLineRunner 只能獲得原始的命令行參數(shù)。

  • ApplicationRunner 可以通過(guò) ApplicationArguments 方便地獲取命令行選項(xiàng)、非選項(xiàng)參數(shù),并區(qū)分它們。

4.實(shí)現(xiàn)原理

既然ApplicationRunner和CommandLineRunner是Spring Boot提供的兩個(gè)擴(kuò)展點(diǎn),我們就來(lái)看看項(xiàng)目啟動(dòng)時(shí)它們是怎么執(zhí)行的。

SpringApplication的核心入口方法#run():

public ConfigurableApplicationContext run(String... args) {
  long startTime = System.nanoTime();
  DefaultBootstrapContext bootstrapContext = createBootstrapContext();
  ConfigurableApplicationContext context = null;
  configureHeadlessProperty();
  SpringApplicationRunListeners listeners = getRunListeners(args);
  listeners.starting(bootstrapContext, this.mainApplicationClass);
  try {
   ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
   ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
   configureIgnoreBeanInfo(environment);
   Banner printedBanner = printBanner(environment);
   context = createApplicationContext();
   context.setApplicationStartup(this.applicationStartup);
   prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
   refreshContext(context);
   afterRefresh(context, applicationArguments);
   Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
   if (this.logStartupInfo) {
    new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
   }
   listeners.started(context, timeTakenToStartup);
      // 執(zhí)行`ApplicationRunner`和`CommandLineRunner`的方法入庫(kù)
   callRunners(context, applicationArguments);
  }
  catch (Throwable ex) {
   handleRunFailure(context, ex, listeners);
   throw new IllegalStateException(ex);
  }
  try {
   Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
   listeners.ready(context, timeTakenToReady);
  }
  catch (Throwable ex) {
   handleRunFailure(context, ex, null);
   throw new IllegalStateException(ex);
  }
  return context;
 }

#callRunners(context, applicationArguments),從方法名就知道實(shí)現(xiàn)ApplicationRunner和CommandLineRunner的核心所在咯。

private void callRunners(ApplicationContext context, ApplicationArguments args) {
  List<Object> runners = new ArrayList<>();
    // 從spring容器中獲取ApplicationRunner類(lèi)型的bean放入到集合runners中
  runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
    // 從spring容器中獲取CommandLineRunner類(lèi)型的bean放入到集合runners中
  runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
    // 排序
  AnnotationAwareOrderComparator.sort(runners);
  for (Object runner : new LinkedHashSet<>(runners)) {
   if (runner instanceof ApplicationRunner) {
    callRunner((ApplicationRunner) runner, args);
   }
   if (runner instanceof CommandLineRunner) {
    callRunner((CommandLineRunner) runner, args);
   }
  }
 }

最后通過(guò)#callRunner()執(zhí)行run方法

private void callRunner(ApplicationRunner runner, ApplicationArguments args) {
  try {
   (runner).run(args);
  }
  catch (Exception ex) {
   throw new IllegalStateException("Failed to execute ApplicationRunner", ex);
  }
 }

5.總結(jié)

CommandLineRunner 和 ApplicationRunner 常用于應(yīng)用啟動(dòng)后的初始化任務(wù)或一次性任務(wù)執(zhí)行。它們?cè)试S你在 Spring 應(yīng)用啟動(dòng)完成后立即執(zhí)行一些邏輯。ApplicationRunner 更適合需要處理命令行參數(shù)的場(chǎng)景,而 CommandLineRunner 更簡(jiǎn)單直接。通過(guò) @Order 注解可以控制多個(gè) Runner 的執(zhí)行順序,確保初始化操作按特定順序進(jìn)行。

本文轉(zhuǎn)載自微信公眾號(hào)「Shepherd進(jìn)階筆記」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系公眾號(hào)。

責(zé)任編輯:武曉燕 來(lái)源: Shepherd進(jìn)階筆記
相關(guān)推薦

2023-12-21 17:11:21

Containerd管理工具命令行

2022-12-20 07:39:46

2023-12-15 09:45:21

阻塞接口

2021-09-02 12:07:48

Swift 監(jiān)聽(tīng)系統(tǒng)Promise

2020-12-18 11:54:22

Linux系統(tǒng)架構(gòu)

2021-06-04 09:35:05

Linux字符設(shè)備架構(gòu)

2021-02-22 09:05:59

Linux字符設(shè)備架構(gòu)

2023-04-04 08:01:47

2022-08-03 08:01:16

CDN網(wǎng)站服務(wù)器

2020-03-18 13:40:03

Spring事數(shù)據(jù)庫(kù)代碼

2021-04-28 08:05:30

SpringCloudEureka服務(wù)注冊(cè)

2022-10-21 17:24:34

契約測(cè)試定位

2023-09-11 06:32:30

VPAHPA容量

2023-11-20 08:18:49

Netty服務(wù)器

2021-05-29 10:11:00

Kafa數(shù)據(jù)業(yè)務(wù)

2023-07-31 08:18:50

Docker參數(shù)容器

2023-11-06 08:16:19

APM系統(tǒng)運(yùn)維

2022-11-11 19:09:13

架構(gòu)

2023-03-31 08:16:53

Flutter優(yōu)化內(nèi)存管理

2023-12-26 08:08:02

Spring事務(wù)MySQL
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)