在Java Spring Boot项目开发中,应用启动后的首次请求往往因为缓存未初始化,需要查询数据库加载数据,导致响应时间较长。缓存预热就是解决这个问题的重要手段,而CommandLineRunner和ApplicationRunner是Spring Boot提供的用于实现启动后执行逻辑的回调接口,非常适合用来实现缓存预热功能。

什么是缓存预热
缓存预热指的是在应用启动阶段,提前将预期会被频繁访问的热点数据从数据库、文件系统等持久化存储中加载到缓存(如Redis、Caffeine等)中,当用户发起请求时可以直接从缓存获取数据,不需要再去查询底层存储,从而提升响应速度,减少数据库压力。
CommandLineRunner接口介绍
CommandLineRunner是Spring Boot提供的一个函数式接口,只有一个run(String... args)方法,该方法会在Spring容器完全初始化完成后执行,方法的参数是应用启动时传入的命令行参数。
基本使用示例
实现CommandLineRunner接口,重写run方法,在方法中编写缓存预热的逻辑:
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
@Component
public class CachePreheatRunner implements CommandLineRunner {
// 模拟缓存组件,实际项目中可替换为RedisTemplate、CaffeineCache等
private Map<String, Object> cache = new HashMap<>();
@Override
public void run(String... args) throws Exception {
System.out.println("开始执行缓存预热逻辑");
// 模拟从数据库查询热点数据
List<String> hotDataList = queryHotDataFromDb();
for (String data : hotDataList) {
cache.put(data, "data_" + data);
}
System.out.println("缓存预热完成,当前缓存数据量:" + cache.size());
}
// 模拟数据库查询方法
private List<String> queryHotDataFromDb() {
List<String> list = new ArrayList<>();
for (int i = 1; i <= 10; i++) {
list.add("hot_key_" + i);
}
return list;
}
}
执行顺序控制
如果有多个类实现了CommandLineRunner接口,可以通过@Order注解或者实现Ordered接口来指定执行顺序,数值越小越先执行:
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Component
@Order(1)
public class FirstPreheatRunner implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
System.out.println("第一个缓存预热任务执行");
}
}
@Component
@Order(2)
public class SecondPreheatRunner implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
System.out.println("第二个缓存预热任务执行");
}
}
ApplicationRunner接口介绍
ApplicationRunner和CommandLineRunner功能类似,也是Spring容器初始化完成后执行的回调接口,区别在于它的run方法参数是ApplicationArguments对象,该对象对命令行参数做了更友好的封装,可以方便获取带key的参数和不带key的参数。
基本使用示例
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
@Component
public class AppCachePreheatRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("ApplicationRunner执行缓存预热");
// 获取命令行参数,例如启动时传入 --env=test
List<String> envList = args.getOptionValues("env");
if (envList != null && !envList.isEmpty()) {
System.out.println("当前环境:" + envList.get(0));
}
// 执行缓存加载逻辑
loadHotDataToCache();
}
private void loadHotDataToCache() {
// 实际缓存加载逻辑,此处省略
System.out.println("热点数据加载到缓存完成");
}
}
CommandLineRunner与ApplicationRunner的区别
两个接口的核心区别如下:
| 对比项 | CommandLineRunner | ApplicationRunner |
|---|---|---|
| run方法参数 | String数组,直接接收原始命令行参数 | ApplicationArguments对象,封装了命令行参数,支持按key获取参数 |
| 参数处理难度 | 需要自行解析参数格式 | 提供了getOptionNames、getOptionValues等方法,处理更方便 |
| 执行顺序 | 都支持@Order注解控制顺序,相同Order时ApplicationRunner先执行 | |
结合Redis实现完整缓存预热
实际项目中通常会使用Redis作为缓存组件,下面演示结合RedisTemplate实现缓存预热的完整示例:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class RedisCachePreheatRunner implements CommandLineRunner {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Override
public void run(String... args) throws Exception {
System.out.println("开始执行Redis缓存预热");
// 模拟查询数据库中的商品热点数据
List<Product> hotProducts = productMapper.selectHotProducts();
for (Product product : hotProducts) {
// 将商品数据存入Redis,key格式为 product:hot:{商品id}
String key = "product:hot:" + product.getId();
redisTemplate.opsForValue().set(key, product);
}
System.out.println("Redis缓存预热完成,共加载" + hotProducts.size() + "条商品数据");
}
// 模拟商品实体类
static class Product {
private Long id;
private String name;
private BigDecimal price;
// getter和setter省略
}
// 模拟商品Mapper,实际项目中替换为MyBatis或JPA的Mapper
// @Autowired
// private ProductMapper productMapper;
private ProductMapper productMapper;
interface ProductMapper {
List<Product> selectHotProducts();
}
}
注意事项
- 缓存预热逻辑如果执行时间过长,会延长应用启动的总时间,建议只加载必要的热点数据,避免加载全量数据。
- 如果预热逻辑中涉及数据库查询,需要注意数据库连接是否已经初始化完成,Spring Boot中CommandLineRunner执行时数据源已经可用,一般不会有问题。
- 如果预热失败,需要考虑是否要影响应用启动,可通过try-catch包裹逻辑,避免预热异常导致应用启动失败。
- 多个Runner的执行顺序要合理规划,避免后面的Runner依赖前面的Runner执行结果但顺序错误的情况。
JavaCommandLineRunnerApplicationRunner缓存预热Spring_Boot修改时间:2026-06-22 02:36:25