目錄
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定義了兩大核心接口實現緩存標準化:
- Cache接口
定義緩存基本操作(get、put、evict),不同緩存技術(如Redis、Ehcache)通過實現該接口完成適配,例如:
RedisCache通過RedisTemplate操作Redis數據
ConcurrentMapCache使用本地內存Map存儲數據
- 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 {@Resourceprivate IRegionService regionService;@Resourceprivate RedisTemplate redisTemplate;@Resourceprivate 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 -> {//區域idLong 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任務調度平臺-阿里云開發者社區