Springcache+xxljob实现定时刷新缓存

目录

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方法,最终找到处理缓存的主逻辑:

java 复制代码
@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/#《分布式任务调度平台XXL-JOB》

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任务调度平台-阿里云开发者社区

相关推荐
小周不摆烂2 小时前
Java Web从入门到精通:全面探索与实战(一)
java
Source.Liu2 小时前
【学Rust写CAD】25 变量类(variable.rs)
后端·rust
cherryc_2 小时前
JavaSE基础——第六章 类与对象(二)
java·开发语言
快来卷java2 小时前
JVM虚拟机篇(三):JVM运行时数据区与方法区详解
java·jvm·mysql
whltaoin3 小时前
Java实现N皇后问题的双路径探索:递归回溯与迭代回溯算法详解
java·算法
nlog3n5 小时前
Java策略模式详解
java·bash·策略模式
冷琅辞7 小时前
Elixir语言的云计算
开发语言·后端·golang
Mryan20058 小时前
解决GraalVM Native Maven Plugin错误:JAVA_HOME未指向GraalVM Distribution
java·开发语言·spring boot·maven
VX_CXsjNo18 小时前
免费送源码:Java+SSM+Android Studio 基于Android Studio游戏搜索app的设计与实现 计算机毕业设计原创定制
java·spring boot·spring·游戏·eclipse·android studio·android-studio
ylfhpy8 小时前
Java面试黄金宝典33
java·开发语言·数据结构·面试·职场和发展·排序算法