简介

在项目实际的开发过程中,有时候会遇到需要在应用程序启动完毕对外提供服务之前预先将部分数据装载到缓存的需求。本文就总结了常见的数据预装载方式及其实践。

适用场景

  • 预装载应用级别数据到缓存:如字典数据、公共的业务数据
  • 系统预热
  • 心跳检测:如在系统启动完毕访问一个外服务接口等场景

常见方式

  • 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

SpringBoot程序预装载数据的更多相关文章

  1. [十五]SpringBoot 之 启动加载数据

    实际应用中,我们会有在项目服务启动的时候就去加载一些数据或做一些事情这样的需求. 为了解决这样的问题,spring Boot 为我们提供了一个方法,通过实现接口 CommandLineRunner 来 ...

  2. DHTMLX 前端框架 建立你的一个应用程序 教程(六)-- 表格加载数据

    从数据库加载数据 这篇我们介绍从MySQL数据库中加载数据到表格 我们使用 MySql的数据库dhtmlx_tutorial 和表contacts 示例使用的是PHP平台和dhtmlxConnecto ...

  3. 微信小程序(五) 利用模板动态加载数据

    利用模板动态加载数据,其实是对上一节静态数据替换成动态数据:

  4. Android应用程序后台加载数据

    从ContentProvider查询你需要显示的数据是比较耗时的.如果你在Activity中直接执行查询的操作,那么有可能导致Activity出现ANR的错误.即使没有发生ANR,用户也容易感知到一个 ...

  5. SpringBoot(七)-- 启动加载数据

    一.场景 实际应用中,我们会有在项目服务启动的时候就去加载一些数据或做一些事情这样的需求.为了解决这样的问题,spring Boot 为我们提供了一个方法,通过实现接口 CommandLineRunn ...

  6. 微信小程序下拉刷新 并重新加载数据

    1.在json页面配置: { "enablePullDownRefresh": true } 2.调用刷新函数 onPullDownRefresh: function() { wx ...

  7. Spring Boot 启动加载数据 CommandLineRunner

    实际应用中,我们会有在项目服务启动的时候就去加载一些数据或做一些事情这样的需求. 为了解决这样的问题,Spring Boot 为我们提供了一个方法,通过实现接口 CommandLineRunner 来 ...

  8. 十三、 Spring Boot 启动加载数据 CommandLineRunner

    实际应用中,我们会有在项目服务启动的时候就去加载一些数据或做一些事情这样的需求. 为了解决这样的问题,spring Boot 为我们提供了一个方法,通过实现接口 CommandLineRunner 来 ...

  9. 第一个SpringBoot程序

    第一个SpringBoot程序 例子来自慕课网廖师兄的免费课程 2小时学会SpringBoot Spring Boot进阶之Web进阶 使用IDEA新建工程,选择Spring Initializr,勾 ...

随机推荐

  1. Kafka 是如何实现高吞吐率的?

    Kafka是分布式消息系统,需要处理海量的消息,Kafka的设计是把所有的消息都写入速度低容量大的硬盘,以此来换取更强的存储能力,但实际上,使用硬盘并没有带来过多的性能损失.kafka主要使用了以下几 ...

  2. Java 中 IO 流分为几种?

    按功能来分:输入流(input).输出流(output).按类型来分:字节流和字符流.字节流和字符流的区别是:字节流按 8 位传输以字节为单位输入输出数据,字符流按 16 位传输以字符为单位输入输出数 ...

  3. 面试问题之计算机网络:TCP三次握手四次挥手

    转载于:https://www.cnblogs.com/Andya/p/7272462.html TCP三次握手: 起初A和B都处于CLOSED关闭状态 B创建TCB,处于LISTEN收听状态,等待A ...

  4. Dubbo 如何停机?

    Dubbo 是通过 JDK 的 ShutdownHook 来完成优雅停机的,所以如果使用 kill -9 PID 等强制关闭指令,是不会执行优雅停机的,只有通过 kill PID 时,才 会执行.

  5. Spring 框架的事务管理有哪些优点?

    它为不同的事务 API 如 JTA,JDBC,Hibernate,JPA 和 JDO,提供 一个不变的编程模式. 它为编程式事务管理提供了一套简单的 API 而不是一些复杂的事务 API 它支持声明式 ...

  6. java弹框

    Java弹窗操作 1.任务简介 本博客介绍两种Java弹窗操作的方法,第一个种是通过弹出对话框显示用户输入的信息,第二种是通过用户输入数字的不同打开不同的程序. 2.弹出对话框的操作 1)任务内容编程 ...

  7. BMZCTF 2020祥云杯到点了

    2020祥云杯到点了 下载附件得到三个word文档,我们打开第一个文档然后将隐藏文字显示出来 得到提示 我们查看属性应该就是日期了我们先把他记录下来 然后打开第二个文档 输入刚刚的密码 在第二个wor ...

  8. Living Documentation

    Living Documentation Living documentation in legacy systems Living documentation, which comes from t ...

  9. 论文解读( N2N)《Node Representation Learning in Graph via Node-to-Neighbourhood Mutual Information Maximization》

    论文信息 论文标题:Node Representation Learning in Graph via Node-to-Neighbourhood Mutual Information Maximiz ...

  10. QT 如何让release生成的EXE可在他人电脑上运行(尝试了全网的方法后,这应该能帮你解决)

    这两天,迅速上手了QT并用其基于C++编写了一个含UI的小程序 跳过中间的开发阶段 当我兴致满满的要将程序打包时,却是费尽周折,搜尽全网的关于QT的打包教程,最后都不顶用. 后面自己和队友的共同发现, ...