SpringBoot程式預裝載資料

Naylor發表於2022-04-28

簡介

在專案實際的開發過程中,有時候會遇到需要在應用程式啟動完畢對外提供服務之前預先將部分資料裝載到快取的需求。本文就總結了常見的資料預裝載方式及其實踐。

適用場景

  • 預裝載應用級別資料到快取:如字典資料、公共的業務資料
  • 系統預熱
  • 心跳檢測:如在系統啟動完畢訪問一個外服務介面等場景

常見方式

  • ApplicationEvent
  • CommandLineRunner
  • ApplicationRunner

ApplicationEvent

應用程式事件,就是釋出訂閱模式。在系統啟動完畢,嚮應用程式註冊一個事件,監聽者一旦監聽到了事件的釋出,就可以做一些業務邏輯的處理了。

既然是釋出-訂閱模式,那麼訂閱者既可以是一個,也可以是多個。

定義event


import org.springframework.context.ApplicationEvent;
public class CacheEvent   extends ApplicationEvent {
    public CacheEvent(Object source) {
        super(source);
    }
}

定義listener


import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Slf4j
@Component
public class CacheEventListener implements ApplicationListener<CacheEvent> {
    @Autowired
    private MaskingService maskingService;
    @Autowired
    private RedisCache redisCache;
    @Override
    public void onApplicationEvent(CacheEvent cacheEvent) {
        log.debug("CacheEventListener-start");
        List<SysMasking> maskings = maskingService.selectAllSysMaskings();
        if (!CollectionUtils.isEmpty(maskings)) {
            log.debug("CacheEventListener-data-not-empty");
            Map<String, List<SysMasking>> cacheMap = maskings.stream().collect(Collectors.groupingBy(SysMasking::getFieldKey));
            cacheMap.keySet().forEach(x -> {
                if (StringUtils.isNotEmpty(x)) {
                    log.debug("CacheEventListener-x={}", x);
                    List<SysMasking> list = cacheMap.get(x);
                    long count = redisCache.setCacheList(RedisKeyPrefix.MASKING.getPrefix() + x, list);
                    log.debug("CacheEventListener-count={}", count);
                } else {
                    log.debug("CacheEventListener-x-is-empty");
                }
            });
        } else {
            log.debug("CacheEventListener-data-is-empty");
        }
        log.debug("CacheEventListener-end");
    }
}


註冊event


@Slf4j
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class BAMSApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(BAMSApplication.class, args);
        log.debug("app-started");
        context.publishEvent(new CacheEvent("處理快取事件"));
    }
}


CommandLineRunner

通過實現 CommandLineRunner 介面,可以在應用程式啟動完畢,回撥到指定的方法中。


package com.ramble.warmupservice.runner;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class CacheCommandLineRunner implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        log.debug("CacheCommandLineRunner-start");
        log.debug("CacheCommandLineRunner-引數={}", args);
        // 注入業務 service ,獲取需要快取的資料
        // 注入 redisTemplate ,將需要快取的資料存放到 redis 中
        log.debug("CacheCommandLineRunner-end");
    }
}


ApplicationRunner

同CommandLineRunner 類似,區別在於,對引數做了封裝。


package com.ramble.warmupservice.runner;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class CacheApplicationRunner implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) throws Exception {
        log.debug("CacheApplicationRunner-start");
        log.debug("CacheApplicationRunner-引數={}", JSON.toJSONString(args));
        // 注入業務 service ,獲取需要快取的資料
        // 注入 redisTemplate ,將需要快取的資料存放到 redis 中
        log.debug("CacheApplicationRunner-end");
    }
}

測試

上述程式碼在idea中啟動,若不帶引數,輸出如下:


2022-04-28 15:44:00.981  INFO 1160 --- [           main] c.r.w.WarmupServiceApplication           : Started WarmupServiceApplication in 1.335 seconds (JVM running for 2.231)
2022-04-28 15:44:00.982 DEBUG 1160 --- [           main] c.r.w.runner.CacheApplicationRunner      : CacheApplicationRunner-start
2022-04-28 15:44:01.025 DEBUG 1160 --- [           main] c.r.w.runner.CacheApplicationRunner      : CacheApplicationRunner-引數={"nonOptionArgs":[],"optionNames":[],"sourceArgs":[]}
2022-04-28 15:44:01.025 DEBUG 1160 --- [           main] c.r.w.runner.CacheApplicationRunner      : CacheApplicationRunner-end
2022-04-28 15:44:01.025 DEBUG 1160 --- [           main] c.r.w.runner.CacheCommandLineRunner      : CacheCommandLineRunner-start
2022-04-28 15:44:01.026 DEBUG 1160 --- [           main] c.r.w.runner.CacheCommandLineRunner      : CacheCommandLineRunner-引數={}
2022-04-28 15:44:01.026 DEBUG 1160 --- [           main] c.r.w.runner.CacheCommandLineRunner      : CacheCommandLineRunner-end
2022-04-28 15:44:01.026 DEBUG 1160 --- [           main] c.r.w.listener.CacheEventListener        : CacheEventListener-start
2022-04-28 15:44:01.026 DEBUG 1160 --- [           main] c.r.w.listener.CacheEventListener        : CacheEventListener-引數=ApplicationEvent-->快取系統資料
2022-04-28 15:44:01.029 DEBUG 1160 --- [           main] c.r.w.listener.CacheEventListener        : CacheEventListener-end
Disconnected from the target VM, address: '127.0.0.1:61320', transport: 'socket'
Process finished with exit code 130


若使用 java -jar xxx.jar --server.port=9009 啟動,則輸入如下:


2022-04-28 16:02:05.327  INFO 9916 --- [           main] c.r.w.WarmupServiceApplication           : Started WarmupServiceApplication in 1.78 seconds (JVM running for 2.116)
2022-04-28 16:02:05.329 DEBUG 9916 --- [           main] c.r.w.runner.CacheApplicationRunner      : CacheApplicationRunner-start
2022-04-28 16:02:05.393 DEBUG 9916 --- [           main] c.r.w.runner.CacheApplicationRunner      : CacheApplicationRunner-引數={"nonOptionArgs":[],"optionNames":["server.port"],"sourceArgs":["--server.port=9009"]}
2022-04-28 16:02:05.395 DEBUG 9916 --- [           main] c.r.w.runner.CacheApplicationRunner      : CacheApplicationRunner-end
2022-04-28 16:02:05.395 DEBUG 9916 --- [           main] c.r.w.runner.CacheCommandLineRunner      : CacheCommandLineRunner-start
2022-04-28 16:02:05.395 DEBUG 9916 --- [           main] c.r.w.runner.CacheCommandLineRunner      : CacheCommandLineRunner-引數=--server.port=9009
2022-04-28 16:02:05.395 DEBUG 9916 --- [           main] c.r.w.runner.CacheCommandLineRunner      : CacheCommandLineRunner-end
2022-04-28 16:02:05.395 DEBUG 9916 --- [           main] c.r.w.listener.CacheEventListener        : CacheEventListener-start
2022-04-28 16:02:05.396 DEBUG 9916 --- [           main] c.r.w.listener.CacheEventListener        : CacheEventListener- 引數=ApplicationEvent-->快取系統資料
2022-04-28 16:02:05.396 DEBUG 9916 --- [           main] c.r.w.listener.CacheEventListener        : CacheEventListener-end


執行順序

從上面測試的輸出,可以看到三種方式執行的順序為:
ApplicationRunner--->CommandLineRunner--->ApplicationEvent

另外,若同時定義多個runner,可以通過order來指定他們的優先順序。

程式碼

https://gitee.com/naylor_personal/ramble-spring-cloud/tree/master/warmup-service

相關文章