SpringBoot程序预装载数据
简介
在项目实际的开发过程中,有时候会遇到需要在应用程序启动完毕对外提供服务之前预先将部分数据装载到缓存的需求。本文就总结了常见的数据预装载方式及其实践。
适用场景
- 预装载应用级别数据到缓存:如字典数据、公共的业务数据
- 系统预热
- 心跳检测:如在系统启动完毕访问一个外服务接口等场景
常见方式
- 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程序预装载数据的更多相关文章
- [十五]SpringBoot 之 启动加载数据
实际应用中,我们会有在项目服务启动的时候就去加载一些数据或做一些事情这样的需求. 为了解决这样的问题,spring Boot 为我们提供了一个方法,通过实现接口 CommandLineRunner 来 ...
- DHTMLX 前端框架 建立你的一个应用程序 教程(六)-- 表格加载数据
从数据库加载数据 这篇我们介绍从MySQL数据库中加载数据到表格 我们使用 MySql的数据库dhtmlx_tutorial 和表contacts 示例使用的是PHP平台和dhtmlxConnecto ...
- 微信小程序(五) 利用模板动态加载数据
利用模板动态加载数据,其实是对上一节静态数据替换成动态数据:
- Android应用程序后台加载数据
从ContentProvider查询你需要显示的数据是比较耗时的.如果你在Activity中直接执行查询的操作,那么有可能导致Activity出现ANR的错误.即使没有发生ANR,用户也容易感知到一个 ...
- SpringBoot(七)-- 启动加载数据
一.场景 实际应用中,我们会有在项目服务启动的时候就去加载一些数据或做一些事情这样的需求.为了解决这样的问题,spring Boot 为我们提供了一个方法,通过实现接口 CommandLineRunn ...
- 微信小程序下拉刷新 并重新加载数据
1.在json页面配置: { "enablePullDownRefresh": true } 2.调用刷新函数 onPullDownRefresh: function() { wx ...
- Spring Boot 启动加载数据 CommandLineRunner
实际应用中,我们会有在项目服务启动的时候就去加载一些数据或做一些事情这样的需求. 为了解决这样的问题,Spring Boot 为我们提供了一个方法,通过实现接口 CommandLineRunner 来 ...
- 十三、 Spring Boot 启动加载数据 CommandLineRunner
实际应用中,我们会有在项目服务启动的时候就去加载一些数据或做一些事情这样的需求. 为了解决这样的问题,spring Boot 为我们提供了一个方法,通过实现接口 CommandLineRunner 来 ...
- 第一个SpringBoot程序
第一个SpringBoot程序 例子来自慕课网廖师兄的免费课程 2小时学会SpringBoot Spring Boot进阶之Web进阶 使用IDEA新建工程,选择Spring Initializr,勾 ...
随机推荐
- Mysql查询优化器之关于JOIN的优化
连接查询应该是比较常用的查询方式,连接查询大致分为:内连接.外连接(左连接和右连接).自然连接 下图展示了 LEFT JOIN.RIGHT JOIN.INNER JOIN.OUTER JOIN 相关的 ...
- kafka follower如何与leader同步数据?
Kafka的复制机制既不是完全的同步复制,也不是单纯的异步复制.完全同步复制要求All Alive Follower都复制完,这条消息才会被认为commit,这种复制方式极大的影响了吞吐率.而异步复制 ...
- 解释 WEB 模块?
Spring 的 WEB 模块是构建在 application context 模块基础之上,提供一个适 合 web 应用的上下文.这个模块也包括支持多种面向 web 的任务,如透明地处理 多个文件上 ...
- Redis缓存穿透、缓存雪崩、缓存击穿
缓存穿透: 缓存穿透,是指查询一个数据库一定不存在的数据.正常的使用缓存流程大致是,数据查询先进行缓存查询,如果key不存在或者key已经过期,再对数据库进行查询,并把查询到的对象,放进缓存.如果 ...
- linux文本编辑器vim详解
vim 1.打开文件 vim [option] - file... 打开文件 +# 打开文件后,让光标处于第#行的行首 +/字符串 打开文件后,光标处于第一个被匹配到字符串的行首 -b file 二进 ...
- 11_二阶系统的单位阶跃响应_详细数学推导部分_2nd order system unit step response
- display:inline-block两端对齐 实现列表
做一个ul li 列表类似这样的平时经常会用到 要是用浮动做还是比较简单的直接左右浮动,清除浮动就可以搞定了,因为最近用display:inline-block用的比较顺手,所以就尝试一下.通过tex ...
- google fonts 国内使用解决方案
由于众所周知的原因,国内使用google font库有很大的问题. 解决方案1:使用国内镜像如360网站卫士常用前端公共库CDN服务 优点:使用方便 缺点:目标用户包含国外的开发者,不清楚国外用户的加 ...
- java中如何使用接口继承(Extending Interfaces)
5.接口继承(Extending Interfaces)和通话talk的功能.而Moto888更为高级,除了照相和通话功能以外,还有mp3的功能.接口继承到底有什么意义呢?马克-to-win:1)通过 ...
- java基础-File
File类 * File更应该叫做一个路径, 文件路径或者文件夹路径 * 路径分为绝对路径和相对路径 * 绝对路径是一个固定的路径,从盘符开始 * 相对路径相对于某个位置,在eclipse下 ...