这个问题说白了就是希望通过预加载数据,达到提拔体系性能和响应速度的效果。像目前在很多场景中都有使用:
- 电商平台的商品分类信息、用户底子资料:避免高并发时数据库被重复查询,低落响应延迟。
- 体系参数设置(如地区编码、权限规则)、国际化资源:减少对设置中心或数据库的依赖,提拔设置读取速度。
- 促销活动的商品库存信息、新闻头条内容:通过预加载防止缓存击穿,应对突发流量。
题目说的是提前加载的redis缓存中,像设置类信息等这种变更频率低、及时性要求低的数据,还会加载到当地缓存中(如GuavaCache,Caffeine等),进一步减轻redis的压力,提拔访问速度
重点
重点着实就是使用Spring 或 SpringBoot的扩展点来完成这部分功能
初始化数据加载触发机制
- 使用 CommandLineRunner或ApplicationRunner 在应用启动时自动执行数据加载逻辑。这是最常见的实现方式。
- @Component
- public class CacheWarmupRunner implements ApplicationRunner {
- @Override
- public void run(ApplicationArguments args) {
- // 分页加载数据到缓存
- PageHelper.startPage(1, 1000);
- List<Product> products = productMapper.selectAll();
- products.forEach(p -> redisTemplate.opsForHash().put("products", p.getId(), p));
- }
- }
复制代码
- 使用 @PostConstruct 注解
在服务类中通过 @Postconstruct 注解标记等初始化方法,在 Bean 创建后立即执行数据加载
- @Service
- public class CachePreloader {
- @Autowired
- private UserService userService;
- @Autowired
- private RedisTemplate<String, Object> redisTemplate;
- @PostConstruct
- public void init() {
- List<User> users = userService.getAllFixedData(); // 从数据库获取数据
- users.forEach(user ->
- redisTemplate.opsForValue().set("user:" + user.getId(), user)
- );
- }
- }
复制代码 结合缓存注解主动触发
使用 @Cacheable 注解:在首次调用方法时触发缓存写入,强制触发缓存写入。但需手动触发首次调用才气完成预加载- @Service
- public class UserService {
- @Cacheable(value = "users", key = "#root.methodName")
- public List<User> getAllFixedData() {
- return userRepository.findAll(); // 首次调用会写入缓存
- }
- }
复制代码 注意
- 推荐方案:使用 CommandlineRunner 或 @PostConstruct 在启动时主动加载数据到Redis,确保缓存立即可用。
- 注解增补:@Cacheable 实用于懒加载场景,但需结合首次调用触发。
- 注意事项:确保实体类实现 Serializable 接口,并正确设置 RedisTemplate 的序列化方式.
扩展知识
关于Spring 和 SpringBoot的扩展点我已经写过一篇文章详细介绍,可以点击检察了解
CommandLineRunner和ApplicationRunner
org.springframework.boot.CommandLineRunner
介绍
这两个是Springboot中新增的扩展点,之所以将这两个扩展点放在一起,是因为它两个功能特性高度相似,不同的只是名字、扩展方法形参数范例、执行先后的一些小的不同。
这两个接口触发机会为整个项目启动完毕后,自动执行。如果有多个CommandLineRunner,可以使用@Order来举行排序。
注意:
- CommandLineRunner和ApplicationRunner都有一个扩展方法run(),但是run()形参数范例不同;
- CommandLineRunner.run()方法的形参数范例是String... args,ApplicationRunner.run()的形参数范例是ApplicationArguments args;
- CommandLineRunner.run()的执行机会要晚于ApplicationRunner.run()一点;
- CommandLineRunner和ApplicationRunner触发执行机会是在Spring容器、Tomcat容器正式启动完成后,可以正式处理业务哀求前,即项目启动的最后一步;
- CommandLineRunner和ApplicationRunner可以应用的场景:项目启动前,热门数据的预加载、清除临时文件、读取自定义设置信息等;
使用场景
- 初始化数据:使用 CommandLineRunner 可以在应用启动后初始化一些必要的数据,例如从数据库加载某些设置或插入初始数据。
- @Component
- public class DataInitializer implements CommandLineRunner {
- @Override
- public void run(String... args) {
- System.out.println("初始化数据:插入初始数据");
- // 模拟插入初始数据
- insertInitialData();
- }
- private void insertInitialData() {
- System.out.println("插入数据:用户表初始数据");
- }
- }
复制代码
- 启动后执行任务:使用 CommandLineRunner 可以在应用启动后执行一些特定的任务,比如发送一个关照或启动一些背景任务。
- @Component
- public class TaskExecutor implements CommandLineRunner {
- @Override
- public void run(String... args) {
- System.out.println("启动后执行任务:发送启动通知");
- // 模拟发送启动通知
- sendStartupNotification();
- }
- private void sendStartupNotification() {
- System.out.println("通知:应用已启动");
- }
- }
复制代码
- 读取命令行参数:使用 CommandLineRunner 可以获取并处理命令行参数,这对于需要根据启动参数动态设置应用的场景非常有用。
- @Component
- public class CommandLineArgsProcessor implements CommandLineRunner {
- @Override
- public void run(String... args) {
- System.out.println("处理命令行参数:");
- for (String arg : args) {
- System.out.println("参数:" + arg);
- }
- }
- }
- @SpringBootApplication
- public class AppConfig {
- public static void main(String[] args) {
- SpringApplication.run(AppConfig.class, new String[]{"参数1", "参数2", "参数3"});
- }
- }
复制代码 @PostConstruct
javax.annotation.PostConstruct
介绍
可以看出来其本身不是Spring定义的注解,但是Spring提供了具体的实现。这个并不算一个扩展点,着实就是一个标注。其作用是在bean的初始化阶段,如果对一个方法标注了@PostConstruct,会先调用这个方法。这里重点是要关注下这个标准的触发点,这个触发点是在postProcessBeforeInitialization之后,InitializingBean.afterPropertiesSet之前。
注意:
- 使用@PostConstruct注解标记的方法不能有参数,除非是拦截器,可以采用拦截器规范定义的InvocationContext对象。
- 使用@PostConstruct注解标记的方法不能有返回值,现实上如果有返回值,也不会报错,但是会忽略掉;
- 使用@PostConstruct注解标记的方法不能被static修饰,但是final是可以的;
使用场景
使用场景与 InitializingBean 类似,具体看下文
InitializingBean
org.springframework.beans.factory.InitializingBean
介绍
这个类,顾名思义,也是用来初始化bean的。InitializingBean接口为bean提供了初始化方法的方式,它只在bean实例化、属性注入后的提供了一个扩展点afterPropertiesSet方法,凡是继承该接口的类,在初始前、属性赋值后,都会执行该方法。这个扩展点的触发机会在postProcessAfterInitialization之前。
注意:
- 与InitializingBean#afterPropertiesSet()类似效果的是init-method,但是需要注意的是InitializingBean#afterPropertiesSet()执行机会要略早于init-method;
- InitializingBean#afterPropertiesSet()的调用方式是在bean初始化过程中真接调用bean的afterPropertiesSet();
- bean自定义属性init-method是通过java反射的方式举行调用 ;
使用场景
- 初始化资源:可以在 Bean 初始化后自动启动一些资源,如数据库毗连、文件读取等。
- import org.springframework.beans.factory.InitializingBean;
- import org.springframework.stereotype.Component;
- @Component
- public class ResourceInitializer implements InitializingBean {
- @Override
- public void afterPropertiesSet() {
- // 模拟资源初始化
- System.out.println("资源初始化:建立数据库连接");
- }
- public void performAction() {
- System.out.println("资源使用:执行数据库操作");
- }
- }
-
- @Configuration
- @ComponentScan(basePackages = "com.seven")
- public class AppConfig {
- public static void main(String[] args) {
- ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
- ResourceInitializer initializer = context.getBean(ResourceInitializer.class);
- initializer.performAction();
- }
- }
复制代码- @Component
- public class InitialValueSetter implements InitializingBean {
- private String initialValue;
- @Override
- public void afterPropertiesSet() {
- initialValue = "默认值";
- System.out.println("设置初始值:" + initialValue);
- }
- public void printValue() {
- System.out.println("当前值:" + initialValue);
- }
- }
- @Configuration
- @ComponentScan(basePackages = "com.seven")
- public class AppConfig {
- public static void main(String[] args) {
- ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
- InitialValueSetter valueSetter = context.getBean(InitialValueSetter.class);
- valueSetter.printValue();
- }
- }
复制代码
- 加载设置:可以在 Bean 初始化后加载必要的设置,如从文件或数据库中读取设置。
- @Component
- public class ConfigLoader implements InitializingBean {
- private String configValue;
- @Override
- public void afterPropertiesSet() {
- // 模拟配置加载
- configValue = "配置值";
- System.out.println("加载配置:" + configValue);
- }
- public void printConfig() {
- System.out.println("当前配置:" + configValue);
- }
- }
- @Configuration
- @ComponentScan(basePackages = "com.seven")
- public class AppConfig {
- public static void main(String[] args) {
- ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
- ConfigLoader configLoader = context.getBean(ConfigLoader.class);
- configLoader.printConfig();
- }
- }
复制代码 免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |