Springcache+xxljob实现定时刷新缓存

news2025/4/7 23:31:38

目录

SpringCache详解

SpringCache概述

核心原理

接口抽象与多态

AOP动态代理

核心注解以及使用

公共属性

cacheNames

KeyGenerator:key生成器

key

condition:缓存的条件,对入参进行判断

注解

xxl-job详解

Springcache+Redis实现缓存

xxl-job定时刷新缓存


SpringCache详解

SpringCache概述

早期的Java开发中,缓存技术需要借助第三方库(Ehcache、Guava等),导致了代码与具体缓存实现强耦合。此时缺乏统一缓存规范,并且在企业中更换缓存组件成本较高。

Spring框架在3.1版本首次引入缓存抽象层,定义了Cache和CacheManager接口,通过注解(如@Cacheable)实现声明式缓存。随后在Spring4.1开始全面支持JCache规范(2012年提出的JSR-107规范草案),通过JCacheManager集成第三方缓存实现,自此开发者可以通过标准接口切换缓存方案,实现了通过标准接口隔离业务代码与具体缓存的抽象解耦,符合“开方-封闭原则”。

Spring Cache 是 Spring 框架提供的抽象化缓存解决方案,通过注解和 AOP 技术简化了缓存逻辑的集成。它并不直接管理缓存存储,而是作为统一接口支持多种缓存实现(如 Ehcache、Redis、Caffeine 等),使开发者能够通过声明式注解快速为方法添加缓存功能,从而减少重复计算,提升系统性能。

核心原理

接口抽象与多态

SpringCache定义了两大核心接口实现缓存标准化:

  1. Cache接口

定义缓存基本操作(get、put、evict),不同缓存技术(如Redis、Ehcache)通过实现该接口完成适配,例如:

RedisCache通过RedisTemplate操作Redis数据

ConcurrentMapCache使用本地内存Map存储数据

  1. CacheManager接口

管理多个Cache实例的生命周期,支持多级缓存混合使用。

(EhCacheCacheManager解析ehcahe.xml配置,RedisCacheManager配置TTL和序列化策略)

AOP动态代理

SpringCache通过CacheInterceptor拦截器实现方法级缓存空值:

说明:

  • invocation.proceed() 封装为 CacheOperationInvoker 实例,延迟执行原始方法。
  • 捕获所有 Throwable 并封装为 ThrowableWrapper,避免缓存逻辑干扰异常类型。
  • 调用 execute:传递方法调用器、目标对象、方法对象和参数,进入缓存处理核心逻辑。

然后一直跟进execute方法,最终找到处理缓存的主逻辑:

@Nullable
private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {
    if (contexts.isSynchronized()) {
        CacheOperationContext context = (CacheOperationContext)contexts.get(CacheableOperation.class).iterator().next();
        if (!this.isConditionPassing(context, CacheOperationExpressionEvaluator.NO_RESULT)) {
            return this.invokeOperation(invoker);
        }

        Object key = this.generateKey(context, CacheOperationExpressionEvaluator.NO_RESULT);
        Cache cache = (Cache)context.getCaches().iterator().next();

        try {
            return this.wrapCacheValue(method, this.handleSynchronizedGet(invoker, key, cache));
        } catch (Cache.ValueRetrievalException var10) {
            Cache.ValueRetrievalException ex = var10;
            ReflectionUtils.rethrowRuntimeException(ex.getCause());
        }
    }

    this.processCacheEvicts(contexts.get(CacheEvictOperation.class), true, CacheOperationExpressionEvaluator.NO_RESULT);
    Cache.ValueWrapper cacheHit = this.findCachedItem(contexts.get(CacheableOperation.class));
    List<CachePutRequest> cachePutRequests = new ArrayList();
    if (cacheHit == null) {
        this.collectPutRequests(contexts.get(CacheableOperation.class), CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);
    }

    Object cacheValue;
    Object returnValue;
    if (cacheHit != null && !this.hasCachePut(contexts)) {
        cacheValue = cacheHit.get();
        returnValue = this.wrapCacheValue(method, cacheValue);
    } else {
        returnValue = this.invokeOperation(invoker);
        cacheValue = this.unwrapReturnValue(returnValue);
    }

    this.collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);
    Iterator var8 = cachePutRequests.iterator();

    while(var8.hasNext()) {
        CachePutRequest cachePutRequest = (CachePutRequest)var8.next();
        cachePutRequest.apply(cacheValue);
    }

    this.processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);
    return returnValue;
}

流程图:

核心注解以及使用

这里可以看这篇博客:SpringCache详解_spring cache-CSDN博客

公共属性

cacheNames

每个注解中都有自己的缓存名字。该名字的缓存与方法相关联,每次调用时,都会检查缓存以查看是否有对应cacheNames名字的数据,避免重复调用方法。名字可以可以有多个,在这种情况下,在执行方法之前,如果至少命中一个缓存,则返回相关联的值。( Springcache提供两个参数来指定缓存名:value、cacheNames,二者选其一即可,每一个需要缓存的数据都需要指定要放到哪个名字的缓存,缓存的分区,按照业务类型分 )

KeyGenerator:key生成器

缓存的本质是key-value存储模式,每一次方法的调用都需要生成相应的Key, 才能操作缓存。

通常情况下,@Cacheable有一个属性key可以直接定义缓存key,开发者可以使用SpEL语言定义key值。若没有指定属性key,缓存抽象提供了 KeyGenerator来生成key ,具体源码如下,

import java.lang.reflect.Method;

public class SimpleKeyGenerator implements KeyGenerator {
    public SimpleKeyGenerator() {
    }

    public Object generate(Object target, Method method, Object... params) {
        return generateKey(params);
    }

    public static Object generateKey(Object... params) {
        if (params.length == 0) {
            return SimpleKey.EMPTY;
        } else {
            if (params.length == 1) {
                Object param = params[0];
                if (param != null && !param.getClass().isArray()) {
                    return param;
                }
            }

            return new SimpleKey(params);
        }
    }
}
  • 如果没有参数,则直接返回SimpleKey.EMPTY
  • 如果只有一个参数,则直接返回该参数;
  • 若有多个参数,则返回包含多个参数的SimpleKey对象。

当然Spring Cache也考虑到需要自定义Key,我们可以通过实现KeyGenerator 接口来重新定义key生成方式

默认的 key 生成器要求参数具有有效的 hashCode() 和 equals() 方法实现。

key

key,缓存的key,如果是redis,则相当于redis的key

可以为空,如果需要可以使用spel表达式进行表写。如果为空,则缺省默认使用key表达式生成器进行生成。默认的 key 生成器要求参数具有有效的 hashCode() 和 equals() 方法实现。key的生成器。key/keyGenerator二选一使用

condition:缓存的条件,对入参进行判断

可以为空,如果需要指定,需要使用SPEL表达式,返回true/false,只有返回true的时候才会对数据源进行缓存/清除缓存。在方法调用之前或之后都能进行判断。

condition=false时,不读取缓存,直接执行方法体,并返回结果,同时返回结果也不放入缓存。

condition=true时,读取缓存,有缓存则直接返回。无则执行方法体,同时返回结果放入缓存(如果配置了result,且要求不为空,则不会缓存结果)。

注意:

condition 属性使用的SpEL语言只有#root和获取参数类的SpEL表达式,不能使用返回结果的#result 。 所以 condition = "#result != null" 会导致所有对象都不进入缓存,每次操作都要经过数据库。

使用实例:

@Override
@Caching(evict = {
    @CacheEvict(value = RedisConstants.CacheName.ZL_CACHE, key = "'ACTIVE_REGIONS'"),
    @CacheEvict(value = RedisConstants.CacheName.SERVE_ICON, key = "#id"),
    @CacheEvict(value = RedisConstants.CacheName.SERVE_TYPE, key = "#id"),
    @CacheEvict(value = RedisConstants.CacheName.HOT_SERVE, key = "#id")
})

注解

@Cacheable:方法执行前查看是否有缓存对应的数据,如果有直接返回数据,如果没有调用方法获取数据返回,并缓存起来,也就是查询数据时缓存,将方法的返回值进行缓存

1、unless:条件符合则不缓存,对出参进行判断

unless属性可以使用#result表达式。效果: 缓存如果有符合要求的缓存数据则直接返回,没有则去数据库查数据,查到了就返回并且存在缓存一份,没查到就不存缓存。

condition 不指定相当于 true,unless 不指定相当于 false

    当 condition = false,一定不会缓存;

    当 condition = true,且 unless = true,不缓存;

    当 condition = true,且 unless = false,缓存;

2、sync:是否使用异步,默认是false.

在一个多线程的环境中,某些操作可能被相同的参数并发地调用,同一个 value 值可能被多次计算(或多次访问 db),这样就达不到缓存的目的。针对这些可能高并发的操作,我们可以使用 sync 参数来告诉底层的缓存提供者将缓存的入口锁住,这样就只能有一个线程计算操作的结果值,而其它线程需要等待。当值为true,相当于同步可以有效的避免缓存击穿的问题。

@CachePut:方法在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中,简单来说就是更新缓存,将方法的返回值放到缓存中

@CacheEvict:用于清空缓存,方法在调用时会从缓存中移除已存储的数据

1、allEntries:是否清空左右缓存。默认为false

当指定了allEntries为true时,Spring Cache将忽略指定的key

2、beforeInvocation:是否在方法执行前就清空,默认为 false(可以看上面的流程图

清除操作默认是在对应方法成功执行之后触发的,即方法如果因为抛出异常而未能成功返回时也不会触发清除操作。使用beforeInvocation可以改变触发清除操作的时间,当我们指定该属性值为true时,Spring会在调用该方法之前清除缓存中的指定元素。

@Caching:组合多个缓存注解

xxl-job详解

在分布式环境下进行任务调度需要使用分布式任务调度平台,XXL-JOB是一个轻量级分布式任务调度平台,其核心设计目标是开发迅速、学习简单、轻量级、易扩展。现已开放源代码并接入多家公司线上产品线,开箱即用。

官网:https://www.xuxueli.com/xxl-job/

文档:https://www.xuxueli.com/xxl-job/#%E3%80%8A%E5%88%86%E5%B8%83%E5%BC%8F%E4%BB%BB%E5%8A%A1%E8%B0%83%E5%BA%A6%E5%B9%B3%E5%8F%B0XXL-JOB%E3%80%8B

XXL-JOB主要有调度中心、执行器、任务:

调度中心:

负责管理调度信息,按照调度配置发出调度亲你跪求,自身不承担业务代码;

主要职责为执行器管理、任务管理、监控运维、日志管理等;

任务执行器:

负责接收调度请求并执行任务逻辑;

主要职责是执行任务、执行结果上报、日志服务等;

使用xxl-job解决多个jvm进程冲入执行任务问题:

XXL-JOB调度中心可以配置路由策略:第一个、轮询策略、分片等

第一个:每次执行任务都由第一个执行器去执行

轮询:执行器轮番执行

分片:每次执行任务广播给每个执行器让他们同时执行任务

如果根据需求每次执行任务仅由一个执行器去执行任务可以设置路由策略:第一个、轮询

如果根据需求每次执行任务由多个执行器同时执行可以设置路由策略:分片

Springcache+Redis实现缓存

这里借用我最近做的一个项目的代码举例:(功能是将访问频率较高的查询开通区域列表接口进行缓存,并且在每天的凌晨1点实现缓存刷新,更新信息

启用区域后删除已开通区域列表缓存,当之后去查询开通区域列表时重新缓存最新的开通区域列表。

/**
     * 已开通服务区域列表
     *
     * @return 区域简略列表
     */
    @Override
    @Cacheable(value = RedisConstants.CacheName.ZL_CACHE, key = "'ACTIVE_REGIONS'", cacheManager = RedisConstants.CacheManager.FOREVER)
    public List<RegionSimpleResDTO> queryActiveRegionListCache() {
        return queryActiveRegionList();
    }

xxl-job定时刷新缓存

foundations包下面的处理器handler类:定义了xxl-job实现缓存同步任务的定时任务

删除缓存->查询开通区域列表进行缓存->遍历区域列表下的服务类型进行缓存

package com.zhilian.foundations.handler;

import com.zhilian.api.foundations.dto.response.RegionSimpleResDTO;
import com.zhilian.foundations.constants.RedisConstants;
import com.zhilian.foundations.service.HomeService;
import com.zhilian.foundations.service.IRegionService;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.List;

/**
 * springCache缓存同步任务
 *
 **/
@Slf4j
@Component
public class SpringCacheSyncHandler {

    @Resource
    private IRegionService regionService;
    @Resource
    private RedisTemplate redisTemplate;

    @Resource
    private HomeService homeService;



    /**
     * 已启用区域缓存更新
     * 每日凌晨1点执行
     */
    @XxlJob(value = "activeRegionCacheSync")
    public void activeRegionCacheSync() {
        log.info(">>>>>>>>开始进行缓存同步,更新已启用区域");

        //删除缓存
        Boolean delete = redisTemplate.delete(RedisConstants.CacheName.ZL_CACHE + "::ACTIVE_REGIONS");

        //通过查询开通区域列表进行缓存
        List<RegionSimpleResDTO> regionSimpleResDTOS = regionService.queryActiveRegionList();

        //遍历区域对该区域下的服务类型进行缓存
        regionSimpleResDTOS.forEach(item -> {
            //区域id
            Long regionId = item.getId();

            //删除该区域下的首页服务列表
            String serve_list_key = RedisConstants.CacheName.SERVE_ICON + "::" + regionId;
            redisTemplate.delete(serve_list_key);
            homeService.queryServeIconCategoryByRegionIdCache(regionId);

            // 删除该区域下的服务类型列表缓存
            String serve_type_key = RedisConstants.CacheName.SERVE_TYPE + "::" + regionId;
            redisTemplate.delete(serve_type_key);
            homeService.queryServeTypeList(regionId);

            // 删除该区域下的服务类型列表缓存
            String serve_hot_list_key = RedisConstants.CacheName.HOT_SERVE + "::" + regionId;
            redisTemplate.delete(serve_hot_list_key);
            homeService.queryHotServeListByRegionIdCache(regionId);
            
            
        });
    }
}

代码写好了,需要去xxl-job调度中心对该定时任务进行管理:

填写任务信息:

说明:

调度类型:CRON

固定速度指按固定的间隔定时调度

Cron,通过Cron表达式实现更丰富的定时调度策略

Cron表达式是一个字符串,通过它可以定义调度策略,格式:

{秒数}{分钟}{小时}{日期}{月份}{星期}{年份(可为空)}

xxl-job提供图形界面配置:

运行模式:BEAN和GLUE,bean模式较常用就是在项目工程中编写执行器的任务代码,GLUE是将任务代码编写在调度中心(Bean模式通常有两种形式的实现:类形式和方法形式,此处使用的是方法形式,即在方法上加上@XxlJob注解

JobHandler即任务方法名,填写任务方法上边@XxlJob注解中的名称

任务配置完成,下边启动任务

启动成功:

我们在任务方法上打断点跟踪,任务方法被执行,如下图:

关于springcache的具体使用,可以看看这个文档:SpringCache详解_spring cache-CSDN博客

关于xxl-job的原理、架构分析以及使用,见这篇文档:

3千字带你搞懂XXL-JOB任务调度平台-阿里云开发者社区

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2328259.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vue2拖拉拽做个模拟公式工具

1. 成图 2. 介绍 就是简单拖拉拽来做个规则运算器&#xff0c;具体运算规则、校验规则自己加。 3. 代码 HTML代码 <template><div class"red-cont"><div class"red-top"><divclass"red-top-left"><div class&quo…

Windows查重工具,强烈推荐大家收藏!

我大家在用电脑的时候&#xff0c;是不是发现用得越久&#xff0c;电脑里的软件和文件就越多&#xff1f; 今天我给大家带来的这两款重复文件查找神器&#xff0c;简直就是电脑里的“清洁小能手”&#xff0c;能帮你把那些重复的文件和文件夹找出来。 Easy DupLicate Finder 重…

使用python完成手写数字识别

入门图像识别的第一个案例,看到好多小伙伴分享,也把自己当初的思路捋捋,写成一篇博客,作为记录和分享,也欢迎各位交流讨论。 实现思路 数据集:MNIST(包含60,000个训练样本和10,000个测试样本) 深度学习框架:Keras(基于TensorFlow) 模型架构:卷积神经网络(CNN) 实…

OpenLayers:如何控制Overlay的层级?

我最近在使用Overlay的时候遇到了一个问题&#xff0c;我向地图中添加了两种不同的Overlay&#xff08;下图中的蓝色标牌和粉色标牌&#xff09;&#xff0c;我希望粉色标牌可以显示在最上层&#xff0c;可偏偏蓝色标牌却将其遮挡住了。于是我对Overlay的层级开始起了兴趣&…

《Golang高性能网络编程:构建低延迟服务器应用》

在本文中&#xff0c;我们将深入探讨Golang高性能网络编程&#xff0c;帮助您构建低延迟服务器应用。我们将介绍Golang的网络编程特性、优化技巧和实际案例&#xff0c;让您更好地理解和应用Golang在网络编程领域的优势。 高性能网络编程简介 什么是Golang高性能网络编程 高性能…

数据结构C语言练习(设计循环队列)

一、循环队列简介 循环队列是一种线性数据结构&#xff0c;基于 FIFO&#xff08;先进先出&#xff09;原则&#xff0c;将队尾连接到队首形成循环。其核心优势是能复用队列之前用过的空间&#xff0c;避免普通队列 “假溢出” 问题。实现时&#xff0c;通常申请 k1 大小的数组…

vscode代码片段的设置与使用

在 Visual Studio Code (VS Code) 中&#xff0c;可以通过自定义**代码片段&#xff08;Snippets&#xff09;**快速插入常用代码模板。以下是详细设置步骤&#xff1a; 步骤 1&#xff1a;打开代码片段设置 按下快捷键 Ctrl Shift P&#xff08;Windows/Linux&#xff09;或…

uniapp -- 列表垂直方向拖拽drag组件

背景 需要在小程序中实现拖拽排序功能,所以就用到了m-drag拖拽组件,在开发的过程中,发现该组件在特殊的场景下会有些问题,并对其进行了拓展。 效果 组件代码 <template><!-- 创建一个垂直滚动视图,类名为m-drag --><scroll

一款非常小的软件,操作起来非常丝滑!

今天我想给大家分享一款超级实用的小软件&#xff0c;它是一款电脑上用的倒计时和关机助手。 关机助手 帮你自动关机 这款关机助手特别小巧&#xff0c;完全不需要安装&#xff0c;文件大小才60KB&#xff0c;比一个小小的文件还小。你只需要把它下载下来&#xff0c;双击打开…

FrameWork基础案例解析(四)

文章目录 单独拉取framework开机与开机动画横屏Android.mk语法单独编译SDKmake 忽略warning单独修改和编译Camera2单独编译Launcher3Android Studio 导入、修改、编译Settings导入 Android Studio 导入、修改、编译Launcher3android 开机默认进入指定Launcher植入自己的apk到系…

通过 C# 提取PDF文档中的图片

当 PDF 文件中包含有价值的图片&#xff0c;如艺术画作、设计素材、报告图表等&#xff0c;提取图片可以将这些图像资源进行单独保存&#xff0c;方便后续在不同的项目中使用&#xff0c;避免每次都要从 PDF 中查找。本文将介绍如何使用C#通过代码从PDF文档中提取图片&#xff…

国标GB28181视频监控平台EasyCVR保驾护航休闲娱乐“九小场所”安全运营

凭借降低人力资源、节约物资成本的优势&#xff0c;在多个场景得到广泛应用。如今&#xff0c;棋牌室、洗浴中心、酒店这类人员频繁流动和密集的场所&#xff0c;已成为安全管理的重点。​ 尽管部分棋牌室已安装了监控设备&#xff0c;但是设备功能单一&#xff0c;只能实现一…

GoLand 2024.3 中文 GO语言开发工具

GoLand 2024.3 中文 GO语言开发工具 文章目录 GoLand 2024.3 中文 GO语言开发工具一、介绍二、效果三、下载 一、介绍 JetBrains GoLand 2024 &#xff0c;是一款GO语言开发工具&#xff0c;全行代码补全&#xff1a;能使用本地运行的上下文感知深度学习模型&#xff0c;可以自…

CentOS 7 强制升级Docker 24.x终极指南(解决MySQL8镜像兼容性问题)

CentOS 7 强制升级Docker 24.x终极指南&#xff08;解决MySQL8镜像兼容性问题&#xff09; 旧版本&#xff1a; 新版本docker&#xff1a; 一、问题背景与方案选型 1.1 典型报错分析 The designated data directory /var/lib/mysql/ is unusable根本原因&#xff1a;旧版…

【区块链安全 | 第十九篇】类型之映射类型

文章目录 映射类型可迭代映射 映射类型 映射类型使用语法 mapping(KeyType KeyName? > ValueType ValueName?)&#xff0c;映射类型的变量声明使用语法 mapping(KeyType KeyName? > ValueType ValueName?) VariableName。 KeyType 可以是任何内置值类型、bytes、st…

Flask与 FastAPI 对比:哪个更适合你的 Web 开发?

在开发 Web 应用时&#xff0c;Python 中有许多流行的 Web 框架可以选择&#xff0c;其中 Flask 和 FastAPI 是两款广受欢迎的框架。它们各有特色&#xff0c;适用于不同的应用场景。本文将从多个角度对比这两个框架&#xff0c;帮助你更好地选择适合的框架来构建你的 Web 应用…

QT 中的元对象系统(五):QMetaObject::invokeMethod的使用和实现原理

目录 1.简介 2.原理概述 3.实现分析 3.1.通过方法名调用方法的实现分析 3.2.通过可调用对象调用方法的实现分析 4.使用场景 5.总结 1.简介 QMetaObject::invokeMethod 是 Qt 框架中的一个静态方法&#xff0c;用于在运行时调用对象的成员函数。这个方法提供了一种动态调…

【无人机】无人机PX4飞控系统高级软件架构

目录 1、概述&#xff08;图解&#xff09; 一、数据存储层&#xff08;Storage&#xff09; 二、外部通信层&#xff08;External Connectivity&#xff09; 三、核心通信枢纽&#xff08;Message Bus&#xff09; 四、硬件驱动层&#xff08;Drivers&#xff09; 五、飞…

【SPP】蓝牙链路控制(LC)在SPP中互操作性深度解析

在蓝牙协议栈的精密分层体系中&#xff0c;其链路控制&#xff08;Link Control, LC&#xff09;层作为基带层的核心组件&#xff0c;承载着物理信道管理、连接建立与维护等关键任务。其互操作性要求直接决定了不同厂商设备能否实现无缝通信。本文将以蓝牙技术规范中的LC互操作…

算法每日一练 (25)

&#x1f4a2;欢迎来到张翊尘的技术站 &#x1f4a5;技术如江河&#xff0c;汇聚众志成。代码似星辰&#xff0c;照亮行征程。开源精神长&#xff0c;传承永不忘。携手共前行&#xff0c;未来更辉煌&#x1f4a5; 文章目录 算法每日一练 (25)四数之和题目描述解题思路解题代码c…