JetCache 缓存框架的使用以及源码分析
一、简介
JetCache
是一个基于Java的缓存系统封装,提供统一的API和注解来简化缓存的使用。 JetCache提供了比SpringCache更加强大的注解,可以原生的支持TTL、两级缓存、分布式自动刷新,还提供了Cache
接口用于手工缓存操作。 当前有四个实现:RedisCache
、RedisLettuceCache
、CaffeineCache
、LinkedHashMapCache
。
特性:
-
通过统一的API访问Cache系统
-
通过注解实现声明式的方法缓存,支持TTL和两级缓存
-
通过注解创建并配置
Cache
实例 -
针对所有
Cache
实例和方法缓存的自动统计 -
Key的生成策略和Value的序列化策略支持自定义配置
-
分布式缓存自动刷新,分布式锁
-
异步Cache API (使用Redis的Lettuce客户端时)
缓存类型:
-
本地
LinkedHashMap
:使用LinkedHashMap做LUR方式淘汰
Caffeine
:基于Java8开发的提供了近乎最佳命中率的高性能的缓存库 -
远程(访问Redis的客户端)
Redis
:使用Jedis客户端,Redis官方首选的Java客户端
RedisSpringData
:使用SpringData访问Redis(官网未作介绍)
RedisLettuce
:使用Lettuce客户端,一个高性能基于Java的Redis驱动框架,支持线程安全的同步、异步操作,底层集成了Project Reactor,提供反应式编程,参考:Redis高级客户端Lettuce详解
为什么使用缓存?
在高并发、大流量等场景下,降低系统延迟,缓解数据库压力,提高系统整体的性能,让用户有更好的体验。
使用场景
读多写少、不追求强一致性、请求入参不易变化
使用规范
选择了远程缓存请设置keyPrefix,保证存放至Redis的缓存key规范化,避免与其他系统出现冲突,例如这样设计:系统简称:所属名字:
,这样存储到Redis的缓存key为:系统简称:所属名字:缓存key
选择了本地缓存请设置limit,全局默认设置了100,本地缓存的数据存放于内存,减轻内存的损耗,如果使用了Caffeine,缓存的key过多可能导致内存溢出
请勿滥用缓存注解,对于非必要添加缓存的方法我们尽量不使用缓存
二、如何使用
说明:以下使用方式是基于SpringBoot引入JetCache
缓存框架的,如果不是SpringBoot工程,请参考JetCache
官网使用
引入maven依赖
添加配置
配置说明
jetcache的全局配置
属性 | 默认值 | 说明 |
---|---|---|
jetcache.statIntervalMinutes | 0 | 用于统计缓存调用相关信息的统计间隔(分钟),0表示不统计。 |
jetcache.areaInCacheName | true | 缓存实例名称cacheName会作为缓存key的前缀,2.4.3以前的版本总是把areaName加在cacheName中,因此areaName也出现在key前缀中。我们一般设置为false。 |
jetcache.penetrationProtect | false | 当缓存访问未命中的情况下,对并发进行的加载行为进行保护。 当前版本实现的是单JVM内的保护,即同一个JVM中同一个key只有一个线程去加载,其它线程等待结果。这是全局配置,如果缓存实例没有指定则使用全局配置。 |
jetcache.enableMethodCache | true | 是否使用jetcache缓存。 |
jetcache.hiddenPackages | 无 | 自动生成缓存实例名称时,为了不让名称太长,hiddenPackages指定的包名前缀会被截掉,多个包名使用逗号分隔。我们一般会指定每个缓存实例的名称。 |
本地缓存的全局配置
属性 | 默认值 | 说明 |
---|---|---|
jetcache.local.${area}.type | 无 | 本地缓存类型,支持 linkedhashmap、caffeine。 |
jetcache.local.${area}.limit | 100 | 每个缓存实例存储的缓存数量的全局配置,仅本地缓存需要配置,如果缓存实例没有指定则使用全局配置,请结合实例的业务场景进行配置该参数。 |
jetcache.local.${area}.keyConvertor | 无 | 缓存key转换器的全局配置,支持的类型:fastjson 。仅当使用@CreateCache且缓存类型为LOCAL时可以指定为none ,此时通过equals方法来识别key。方法缓存必须指定keyConvertor。支持自定义转换器函数,可设置为:bean:beanName ,然后会从spring容器中获取该bean。 |
jetcache.local.${area}.expireAfterWriteInMillis | 无穷大 | 本地缓存超时时间的全局配置(毫秒)。 |
jetcache.local.${area}.expireAfterAccessInMillis | 0 | 多长时间没访问就让缓存失效的全局配置(毫秒),仅支持本地缓存。0表示不使用这个功能。 |
远程缓存的全局配置
属性 | 默认值 | 说明 |
---|---|---|
jetcache.remote.${area}.type | 无 | 连接Redis的客户端类型,支持 redis 、redis.lettuce 、redis.springdata 。 |
jetcache.remote.${area}.keyPrefix | 无 | 保存至远程缓存key的前缀,请规范使用。 |
jetcache.remote.${area}.keyConvertor | 无 | 参考上述说明。 |
jetcache.remote.${area}.valueEncoder | java | 保存至远程缓存value的编码函数,支持:java 、kryo 。支持自定义编码函数,可设置为:bean:beanName ,然后会从spring容器中获取该bean。 |
jetcache.remote.${area}.valueDecoder | java | 保存至远程缓存value的解码函数,支持:java 、kryo 。支持自定义解码函数,可设置为:bean:beanName ,然后会从spring容器中获取该bean。 |
jetcache.remote.${area}.expireAfterWriteInMillis | 无穷大 | 远程缓存超时时间的全局配置(毫秒)。 |
jetcache.remote.${area}.uri | 无 | redis节点信息。 |
上表中${area}对应@Cached和@CreateCache的area属性,如果注解上没有指定area,默认值是"default"。
关于缓存的超时时间:
- put等方法上指定了超时时间,则以此时间为准;
- put等方法上未指定超时时间,使用Cache实例的默认超时时间;
- Cache实例的默认超时时间,通过在@CreateCache和@Cached上的expire属性指定,如果没有指定,使用yml中定义的全局配置,例如@Cached(cacheType=local)使用jetcache.local.default.expireAfterWriteInMillis,如果仍未指定则是无穷大。
注解说明
如果需要使用jetcache
缓存,启动类添加两个注解:@EnableCreateCacheAnnotation
、@EnableMethodCache
@EnableCreateCacheAnnotation
开启可通过@CreateCache注解创建Cache实例功能。
@EnableMethodCache
开启可通过@Cached注解创建Cache实例功能,初始化spring aop,注解说明:
属性 | 默认值 | 说明 |
---|---|---|
basePackages | 无 | jetcache需要拦截的包名,只有这些包名下的Cache实例才会生效 |
order | Ordered.LOWEST_PRECEDENCE | 指定AOP切面执行过程的顺序,默认最低优先级 |
mode | AdviceMode.PROXY | Spring AOP的模式,目前就提供默认值让你修改 |
proxyTargetClass | false | 无 |
@Cached
为一个方法添加缓存,创建对应的缓存实例,注解可以添加在接口或者类的方法上面,该类必须是spring bean,注解说明:
属性 | 默认值 | 说明 |
---|---|---|
area | "default" | 如果在配置中配置了多个缓存area,在这里指定使用哪个area。 |
name | 未定义 | 指定缓存实例名称,如果没有指定,会根据类名+方法名自动生成。name会被用于远程缓存的key前缀。另外在统计中,一个简短有意义的名字会提高可读性。 |
enabled | true | 是否激活缓存。 |
timeUnit | TimeUnit.SECONDS | 指定expire的单位。 |
expire | 未定义 | 超时时间。如果注解上没有定义,会使用全局配置,如果此时全局配置也没有定义,则为无穷大。 |
localExpire | 未定义 | 仅当cacheType为BOTH时适用,为本地缓存指定一个不一样的超时时间,通常应该小于expire。如果没有设置localExpire且cacheType为BOTH,那么本地缓存的超时时间和远程缓存保持一致。 |
cacheType | CacheType.REMOTE | 缓存的类型,支持:REMOTE 、LOCAL 、BOTH ,如果定义为BOTH,会使用LOCAL和REMOTE组合成两级缓存。 |
localLimit | 未定义 | 如果cacheType为LOCAL或BOTH,这个参数指定本地缓存的最大元素数量,以控制内存占用。如果注解上没有定义,会使用全局配置,如果此时你没有定义全局配置,则使用默认的全局配置100。请结合实际业务场景进行设置该值。 |
serialPolicy | 未定义 | 指定远程缓存VALUE的序列化方式,支持SerialPolicy.JAVA 、SerialPolicy.KRYO 。如果注解上没有定义,会使用全局配置,如果你没有定义全局配置,则使用默认的全局配置SerialPolicy.JAVA。 |
keyConvertor | 未定义 | 指定KEY的转换方式,用于将复杂的KEY类型转换为缓存实现可以接受的类型,支持:KeyConvertor.FASTJSON 、KeyConvertor.NONE 。NONE表示不转换,FASTJSON可以将复杂对象KEY转换成String。如果注解上没有定义,会使用全局配置。 |
key | 未定义 | 使用SpEL指定缓存key,如果没有指定会根据入参自动生成。 |
cacheNullValue | false | 当方法返回值为null的时候是否要缓存。 |
condition | 未定义 | 使用SpEL指定条件,如果表达式返回true的时候才去缓存中查询。 |
postCondition | 未定义 | 使用SpEL指定条件,如果表达式返回true的时候才更新缓存,该评估在方法执行后进行,因此可以访问到#result。 |
@CacheInvalidate
用于移除缓存,配置说明:
配置 | 默认值 | 说明 |
---|---|---|
area | "default" | 如果在配置中配置了多个缓存area,在这里指定使用哪个area。 |
name | 无 | 指定缓存的唯一名称,一般指向对应的@Cached定义的name。 |
key | 未定义 | 使用SpEL指定key,如果没有指定会根据入参自动生成。 |
condition | 未定义 | 使用SpEL指定条件,如果表达式返回true才执行删除,可访问方法结果#result。删除缓存实例中key的元素。 |
multi | false | 如果根据SpEL指定的key是一个集合,是否从缓存实例中删除对应的每个缓存。如果设置为true,但是key不是集合,则不会删除缓存。 |
@CacheUpdate
用于更新缓存,配置说明:
配置 | 默认值 | 说明 |
---|---|---|
area | "default" | 如果在配置中配置了多个缓存area,在这里指定使用哪个area。 |
name | 无 | 指定缓存的唯一名称,一般指向对应的@Cached定义的name。 |
key | 未定义 | 使用SpEL指定key,如果没有指定会根据入参自动生成。 |
value | 无 | 使用SpEL指定value。 |
condition | 未定义 | 使用SpEL指定条件,如果表达式返回true才执行更新,可访问方法结果#result。更新缓存实例中key的元素。 |
multi | false | 如果根据SpEL指定key和value都是集合并且元素的个数相同,则是否更新缓存实例中的对应的每个元素。如果设置为true,但是key不是集合或者value不是集合或者它们的元素的个数不相同,也不会更新缓存。 |
@CacheRefresh
用于自定刷新缓存,配置说明:
配置 | 默认值 | 说明 |
---|---|---|
refresh | 无 | 刷新间隔 |
stopRefreshAfterLastAccess | 未定义 | 指定该key多长时间没有访问就停止刷新,如果不指定会一直刷新。 |
refreshLockTimeout | 60秒 | 类型为BOTH/REMOTE的缓存刷新时,同时只会有一台服务器在刷新,这台服务器会在远程缓存放置一个分布式锁,此配置指定该锁的超时时间。 |
timeUnit | TimeUnit.SECONDS | 指定refresh时间单位。 |
@CachePenetrationProtect
当缓存访问未命中的情况下,对并发进行的加载行为进行保护。 当前版本实现的是单JVM内的保护,即同一个JVM中同一个key只有一个线程去加载,其它线程等待结果,配置说明:
配置 | 默认值 | 说明 |
---|---|---|
value | true | 是否开启保护模式。 |
timeout | 未定义 | 其他线程的等待超时时间,如果超时则自己执行方法直接返回结果。 |
timeUnit | TimeUnit.SECONDS | 指定timeout时间单位。 |
@CreateCache
在Spring Bean中使用该注解可创建一个Cache实例,配置说明:
配置 | 默认值 | 说明 |
---|---|---|
area | "default" | 如果在配置中配置了多个缓存area,在这里指定使用哪个area。 |
name | 未定义 | 指定缓存实例名称,如果没有指定,会根据类名+方法名自动生成。name会被用于远程缓存的key前缀。另外在统计中,一个简短有意义的名字会提高可读性。 |
timeUnit | TimeUnit.SECONDS | 指定expire的单位。 |
expire | 未定义 | 超时时间。如果注解上没有定义,会使用全局配置,如果此时全局配置也没有定义,则为无穷大。 |
localExpire | 未定义 | 仅当cacheType为BOTH时适用,为本地缓存指定一个不一样的超时时间,通常应该小于expire。如果没有设置localExpire且cacheType为BOTH,那么本地缓存的超时时间和远程缓存保持一致。 |
cacheType | CacheType.REMOTE | 缓存的类型,支持:REMOTE 、LOCAL 、BOTH ,如果定义为BOTH,会使用LOCAL和REMOTE组合成两级缓存。 |
localLimit | 未定义 | 如果cacheType为LOCAL或BOTH,这个参数指定本地缓存的最大元素数量,以控制内存占用。如果注解上没有定义,会使用全局配置,如果此时你没有定义全局配置,则使用默认的全局配置100。请结合实际业务场景进行设置该值。 |
serialPolicy | 未定义 | 指定远程缓存VALUE的序列化方式,支持SerialPolicy.JAVA 、SerialPolicy.KRYO 。如果注解上没有定义,会使用全局配置,如果你没有定义全局配置,则使用默认的全局配置SerialPolicy.JAVA。 |
keyConvertor | 未定义 | 指定KEY的转换方式,用于将复杂的KEY类型转换为缓存实现可以接受的类型,支持:KeyConvertor.FASTJSON 、KeyConvertor.NONE 。NONE表示不转换,FASTJSON可以将复杂对象KEY转换成String。如果注解上没有定义,会使用全局配置。 |
使用示例
如上述所示
getValue方法会创建一个缓存实例,通过@Cached
注解可以看到缓存实例名称cacheName
为'JetCacheExampleService.getValue',缓存的有效时长为6小时,本地缓存的数量最多为50,缓存类型为BOTH
(优先从本地缓存获取);通过@CacheRefresh
注解可以看到会为该缓存实例设置一个刷新策略,刷新间隔为1小时,2个小时没访问后不再刷新,需要刷新的缓存实例会为其每一个缓存数据创建一个RefreshTask
周期性任务;@CachePenetrationProtect
注解表示该缓存实例开启保护模式,当缓存未命中,同一个JVM中同一个key只有一个线程去加载数据,其它线程等待结果。
updateValue方法可以更新缓存,通过@CacheUpdate
注解可以看到会更新缓存实例'JetCacheExampleService.getValue'中缓存key为#user.userId的缓存value为#user。
deleteValue方法可以删除缓存,通过@CacheInvalidate
注解可以看到会删除缓存实例'JetCacheExampleService.getValue'中缓存key为#user.userId缓存数据。
exampleCache字段会作为一个缓存实例对象,通过@CreateCache
注解可以看到,会将该字段作为cacheName
为'JetCacheExampleService.getValue'缓存实例对象,本地缓存的数量最多为50,缓存类型为LOCAL
,@CachePenetrationProtect
注解表示该缓存实例开启保护模式。
我的业务场景是使用上述的getValue方法创建缓存实例即可。
注意:
@Cached
注解不能和@CacheUpdate
或者@CacheInvalidate
同时使用@CacheInvalidate
可以多个同时使用
另外通过@CreateCache注解创建缓存实例也可以这样初始化:
更加详细的使用方法请参考JetCache
官方地址。
三、源码解析
参考本人Git仓库中的JetCache
项目,已做详细的注释。
简单概括:利用Spring AOP功能,在调用需要缓存的方法前,通过解析注解获取缓存配置,根据这些配置创建不同的实例对象,进行缓存等操作。
JetCache
分为两部分,一部分是Cache API以及实现,另一部分是注解支持。
项目的各个子模块
-
jetcache-anno-api:定义
JetCache
注解和常量。 -
jetcache-core:核心API,Cache接口的实现,提供各种缓存实例的操作,不依赖于Spring。
-
jetcache-autoconfigure:完成初始化,解析application.yml配置文件中的相关配置,以提供不同缓存实例的
CacheBuilder
构造器 -
jetcache-anno:基于Spring提供
@Cached
和@CreateCache
注解支持,初始化Spring AOP以及JetCache
注解等配置。 -
jetcache-redis:使用Jedis提供Redis支持。
-
jetcache-redis-lettuce:使用Lettuce提供Redis支持,实现了
JetCache
异步访问缓存的的接口。 -
jetcache-redis-springdata:使用Spring Data提供Redis支持。
-
jetcache-starter-redis:提供pom文件,Spring Boot方式的Starter,基于Jedis。
-
jetcache-starter-redis-lettuce:提供pom文件,Spring Boot方式的Starter,基于Lettuce。
-
jetcache-starter-redis-springdata:提供pom文件,Spring Boot方式的Starter,基于Spring Data。
-
jetcache-test:提供相关测试。
常用注解与变量

在jetcache-anno-api模块中定义了需要用的缓存注解与常量,在上述已经详细的讲述过,其中@CacheInvalidateContainer
注解定义value为@CacheInvalidate
数组,然后通过jdk8新增的@Repeatable
注解,在@CacheInvalidate
注解上面添加@Repeatable(CacheInvalidateContainer.class)
,即可支持同一个地方可以使用多个@CacheInvalidate
注解。
缓存API
主要查看jetcache-core子模块,提供各种Cache
缓存,以支持不同的缓存类型
Cache接口的子关系,结构如下图:

主要对象描述:
- Cache:缓存接口,定义基本方法
- AbstractCache:抽象类,缓存接口的继承者,提供基本实现,具体实现交由不同的子类
- LinkedHashMapCache:基于LinkedHashMap设计的简易内存缓存
- CaffeineCache:基于Caffeine工具设计的内存缓存
- RedisCache:Redis实现,使用Jedis客户端
- RedisLettuceCache:Redis实现,使用Lettuce客户端
- MultiLevelCache:两级缓存,用于封装EmbeddedCache(本地缓存)和ExternalCache(远程缓存)
- RefreshCache:基于装饰器模式Decorator,提供自动刷新功能
- LazyInitCache:用于@CreateCache注解创建的缓存实例,依赖于Spring
Cache接口
com.alicp.jetcache.Cache
接口,定义了缓存实例的操作方法(部分有默认实现),以及获取分布式锁(非严格,用于刷新远程缓存)的实现,因为继承了java.io.Closeable
接口,所以也提供了close方法的默认实现,空方法,交由不同缓存实例的实现去实现该方法用于释放资源,在com.alicp.jetcache.anno.support.ConfigProvider.doShutdown()
方法中会调用每个缓存实例对象的close方法进行资源释放。主要代码如下:
com.alicp.jetcache.Cache
定义的方法大都是关于缓存的获取、删除和存放操作
-
其中大写的方法返回
JetCache
自定义的CacheResult(完整的返回值,可以清晰的知道执行结果,例如get返回null的时候,无法断定是对应的key不存在,还是访问缓存发生了异常) -
小写的方法默认实现就是调用大写的方法
-
computeIfAbsent
方法最为核心,交由子类去实现 -
tryLockAndRun
方法会非堵塞的尝试获取一把AutoReleaseLock分布式锁(非严格),获取过程:- 尝试往Redis中设置(已存在无法设置)一个键值对,key为缓存
key_#RL#
,value为UUID
,并设置这个键值对的过期时间为60秒(默认) - 如果获取到锁后进行加载任务,也就是重新加载方法并更新远程缓存
- 该锁实现了java.lang.AutoCloseable接口,使用try-with-resource方式,在执行完加载任务后会自动释放资源,也就是调用close方法将获取锁过程中设置的键值对从Redis中删除
- 在RefreshCache中会调用该方法,因为如果存在远程缓存需要刷新则需要采用分布式锁的方式
- 尝试往Redis中设置(已存在无法设置)一个键值对,key为缓存
AbstractCache抽象类
com.alicp.jetcache.AbstractCache
抽象类,实现了Cache接口,主要代码如下:
com.alicp.jetcache.AbstractCache
实现了Cache
接口的大写方法,内部调用自己定义的抽象方法(以DO_
开头,交由不同的子类实现),操作缓存后发送相应的事件CacheEvent
,也就是调用自己定义的notify方法,遍历每个CacheMonitor
对该事件进行后置操作,用于统计信息。
computeIfAbsentImpl
方法实现了Cache
接口的核心方法,从缓存实例中根据缓存key获取缓存value,逻辑如下:
-
获取cache的targetCache,因为我们通过
@CreateCache
注解创建的缓存实例将生成LazyInitCache
对象,需要调用其getTargetCache方法才会完成缓存实例的初始化 -
loader函数是对加载原有方法的封装,这里再进行一层封装,封装成
ProxyLoader
类型,目的是在加载原有方法后将发送CacheLoadEvent
事件 -
从缓存实例中获取对应的缓存value,如果缓存实例对象是
RefreshCache
类型(在com.alicp.jetcache.anno.support.CacheContext.buildCache
方法中会将cache包装成CacheHandlerRefreshCache
),则调用RefreshCache.addOrUpdateRefreshTask
方法,判断是否应该为它添加一个定时的刷新任务 -
如果缓存未命中,则执行loader函数,如果开启了保护模式,则调用自定义的synchronizedLoad方法,大致逻辑:根据缓存key从自己的loaderMap(线程安全)遍历中尝试获取(不存在则创建)
LoaderLock
加载锁,获取到这把加载锁才可以执行loader函数,如果已被其他线程占有则进行等待(没有设置超时时间则一直等待),通过CountDownLatch
计数器实现
AbstractEmbeddedCache本地缓存
com.alicp.jetcache.embedded.AbstractEmbeddedCache
抽象类继承AbstractCache抽象类,定义了本地缓存的存放缓存数据的对象为com.alicp.jetcache.embedded.InnerMap
接口和一个初始化该接口的createAreaCache抽象方法,基于InnerMap接口实现以DO_
开头的方法,完成缓存实例各种操作的具体实现,主要代码如下:
com.alicp.jetcache.embedded.AbstractEmbeddedCache
抽象类实现了操作本地缓存的相关方法
-
定义了缓存实例对象本地缓存的配置信息
EmbeddedCacheConfig
对象 -
定义了缓存实例对象本地缓存基于内存操作缓存数据的
InnerMap
对象,它的初始化过程交由不同的内存缓存实例(LinkedHashMapCache和CaffeineCache)
LinkedHashMapCache
com.alicp.jetcache.embedded.LinkedHashMapCache
基于LinkedHashMap完成缓存实例对象本地缓存基于内存操作缓存数据的InnerMap
对象的初始化工作,主要代码如下:
com.alicp.jetcache.embedded.LinkedHashMapCache
自定义LRUMap
继承LinkedHashMap并实现InnerMap接口
-
自定义
max
字段,存储元素个数的最大值,并设置初始容量为(max * 1.4f) -
自定义
lock
字段,每个缓存实例的锁,通过synchronized关键词保证线程安全,所以性能相对来说不好 -
覆盖LinkedHashMap的
removeEldestEntry
方法,当元素大于最大值时移除最老的元素 -
自定义
cleanExpiredEntry
方法,遍历Map,根据缓存value(被封装成的com.alicp.jetcache.CacheValueHolder
对象,包含缓存数据、失效时间戳和第一次访问的时间),清理过期的元素 -
该对象初始化时会被添加至
com.alicp.jetcache.embedded.Cleaner
清理器中,Cleaner会周期性(每隔60秒)遍历LinkedHashMapCache缓存实例,调用其cleanExpiredEntry方法
Cleaner清理器
com.alicp.jetcache.embedded.Cleaner
用于清理缓存类型为LinkedHashMapCache的缓存数据,请查看相应注释,代码如下:
CaffeineCache
com.alicp.jetcache.embedded.CaffeineCache
基于Caffeine完成缓存实例对象本地缓存基于内存操作缓存数据的InnerMap
对象的初始化工作,主要代码如下:
com.alicp.jetcache.embedded.CaffeineCache
通过Caffeine构建一个com.github.benmanes.caffeine.cache.Cache
缓存对象,然后实现InnerMap接口,调用这个缓存对象的相关方法
-
构建时设置每个元素的过期时间,也就是根据每个元素(
com.alicp.jetcache.CacheValueHolder
)的失效时间戳来设置,底层如何实现的可以参考Caffeine官方地址 -
调用
com.github.benmanes.caffeine.cache.Cache
的put方法我有遇到过'unable to create native thread'内存溢出的问题,所以请结合实际业务场景合理的设置缓存相关配置
AbstractExternalCache远程缓存
com.alicp.jetcache.embedded.AbstractExternalCache
抽象类继承AbstractCache抽象类,定义了缓存实例对象远程缓存的配置信息ExternalCacheConfig
对象,提供了将缓存key转换成字节数组的方法,代码比较简单。
RedisCache
com.alicp.jetcache.redis.RedisCache
使用Jedis连接Redis,对远程的缓存数据进行操作,代码没有很复杂,可查看我的注释
-
定义了
com.alicp.jetcache.redis.RedisCacheConfig
配置对象,包含Redis连接池的相关信息 -
实现了以
DO_
开头的方法,也就是通过Jedis操作缓存数据
RedisLettuceCache
com.alicp.jetcache.redis.lettuce.RedisLettuceCache
使用Lettuce连接Redis,对远程的缓存数据进行操作,代码没有很复杂,可查看我的注释
-
定义了
com.alicp.jetcache.redis.lettuce.RedisLettuceCacheConfig
配置对象,包含Redis客户端、与Redis建立的安全连接等信息,因为底层是基于Netty实现的,所以无需配置线程池 -
使用
com.alicp.jetcache.redis.lettuce.LettuceConnectionManager
自定义管理器将与Redis连接的相关信息封装成LettuceObjects
对象,并管理RedisClient与LettuceObjects对应关系 -
相比Jedis更加安全高效
-
对Lettuce不了解的可以参考我写的测试类
com.alicp.jetcache.test.external.LettuceTest
MultiLevelCache两级缓存
当你设置了缓存类型为BOTH两级缓存,那么创建的实例对象会被封装成com.alicp.jetcache.MultiLevelCache
对象
-
定义了
caches
字段类型为Cache[],用于保存AbstractEmbeddedCache本地缓存实例和AbstractExternalCache远程缓存实例,本地缓存存放于远程缓存前面 -
实现了
do_GET
方法,遍历caches数组,也就是先从本地缓存获取,如果获取缓存不成功则从远程缓存获取,成功获取到缓存后会调用checkResultAndFillUpperCache方法 -
从
checkResultAndFillUpperCache
方法的逻辑可以看到,将获取到的缓存数据更新至更底层的缓存中,也就是说如果缓存数据是从远程获取到的,那么进入这个方法后会将获取到的缓存数据更新到本地缓存中去,这样下次请求可以直接从本地缓存获取,避免与Redis之间的网络消耗 -
实现了
do_PUT
方法,遍历caches数组,通过CompletableFuture
进行异步编程,将所有的操作绑定在一条链上执行。 -
实现的了
PUT(K key, V value)
方法,会先判断是否单独配置了本地缓存时间localExipre,配置了则单独为本地缓存设置过期时间,没有配置则到期时间和远程缓存的一样 -
覆盖
tryLock
方法,调用caches[caches.length-1].tryLock方法,也就是只会调用最顶层远程缓存的这个方法
主要代码如下:
RefreshCache
com.alicp.jetcache.RefreshCache
为缓存实例添加刷新任务,前面在AbstractCache抽象类中讲到了,在com.alicp.jetcache.anno.support.CacheContext.buildCache
方法中会将cache包装成CacheHandlerRefreshCache
,所以说每个缓存实例都会调用一下addOrUpdateRefreshTask
方法,代码如下:
如果缓存实例配置了刷新策略并且刷新间隔大于0,则会从taskMap
(线程安全)中尝试获取对应的刷新任务RefreshTask
,如果不存在则创建一个任务放入线程池周期性的执行
com.alicp.jetcache.RefreshCache.RefreshTask
代码如下:
刷新逻辑:
-
判断是否需要停止刷新了,需要的话调用其
future
的cancel方法取消执行,并从taskMap
中删除 -
获取缓存实例对象,如果是多层则返回顶层,也就是远程缓存实例对象
-
如果是本地缓存,则调用
load
方法,也就是执行loader函数加载原有方法,将获取到的数据更新至缓存实例中(如果是多级缓存,则每级缓存都会更新) -
如果是远程缓存对象,则调用
externalLoad
方法,刷新后会往Redis中存放一个键值对,key为key_#TS#
,value为上一次刷新时间
-
先从Redis中获取上一次刷新时间的键值对,根据上一次刷新的时间判断是否大于刷新间隔,大于(或者没有上一次刷新时间)表示需要重新加载数据,否则不需要重新加载数据
-
如果不需要重新加载数据,但是又是多级缓存,则获取远程缓存数据更新至本地缓存,保证两级缓存的一致性
-
如果需要重新加载数据,则调用
tryLockAndRun
方法,尝试获取分布式锁,执行刷新任务(调用load
方法,并往Redis中重新设置上一次的刷新时间),如果没有获取到分布式锁,则创建一个延迟任务(1/5刷新间隔后)将最顶层的缓存数据更新至每一层
-
解析配置
主要查看jetcache-autoconfigure子模块,解析application.yml中jetcache相关配置,初始化不同缓存类型的CacheBuilder
构造器,用于生产缓存实例,也初始化以下对象:
com.alicp.jetcache.anno.support.ConfigProvider
:缓存管理器,注入了全局配置GlobalCacheConfig、缓存实例管理器SimpleCacheManager、缓存上下文CacheContext等大量信息
com.alicp.jetcache.autoconfigure.AutoConfigureBeans
:存储CacheBuilder
构造器以及Redis的相关信息
com.alicp.jetcache.anno.support.GlobalCacheConfig
:全局配置类,保存了一些全局信息
初始化构造器
通过@Conditional
注解将需要使用到的缓存类型对应的构造器初始化类注入到Spring容器并执行初始化过程,也就是创建CacheBuilder构造器
初始化构造器类的类型结构如下图所示:

主要对象描述:
AbstractCacheAutoInit:抽象类,实现Spring的InitializingBean接口,注入至Spring容器时完成初始化
EmbeddedCacheAutoInit:抽象类,继承AbstractCacheAutoInit,解析本地缓存独有的配置
LinkedHashMapAutoConfiguration:初始化LinkedHashMapCacheBuilder构造器
CaffeineAutoConfiguration:初始化CaffeineCacheBuilder构造器
ExternalCacheAutoInit:抽象类,继承AbstractCacheAutoInit,解析远程缓存独有的配置
RedisAutoInit:初始化RedisCacheBuilder构造器
RedisLettuceAutoInit:初始化RedisLettuceCacheBuilder构造器
AbstractCacheAutoInit
com.alicp.jetcache.autoconfigure.AbstractCacheAutoInit
抽象类主要实现了Spring的InitializingBean接口,在注入Spring容器时,Spring会调用其afterPropertiesSet方法,完成本地缓存类型和远程缓存类型CacheBuilder
构造器的初始化,主要代码如下:
- 在
afterPropertiesSet()
方法中可以看到会调用process
方法分别初始化本地缓存和远程缓存的构造器 - 定义的
process
方法:- 首先会从当前环境中解析出JetCache的相关配置到ConfigTree对象中
- 然后遍历缓存区域,获取对应的缓存类型type,进行不同类型的缓存实例CacheBuilder构造器初始化过程
- 不同CacheBuilder构造器的初始化方法
initCache
交由子类实现 - 获取到CacheBuilder构造器后会将其放入
AutoConfigureBeans
对象中去
- 另外也定义了
parseGeneralConfig
方法解析本地缓存和远程缓存都有的配置至CacheBuilder构造器中
EmbeddedCacheAutoInit
com.alicp.jetcache.autoconfigure.EmbeddedCacheAutoInit
抽象类继承了AbstractCacheAutoInit
,主要是覆盖父类的parseGeneralConfig
,解析本地缓存单有的配置limit
,代码如下:
LinkedHashMapAutoConfiguration
com.alicp.jetcache.autoconfigure.LinkedHashMapAutoConfiguration
继承了EmbeddedCacheAutoInit
,实现了initCache
方法,先通过LinkedHashMapCacheBuilder创建一个默认实现类,然后解析相关配置至构造器中完成初始化,代码如下:
-
这里我们注意到
@Conditional
注解,这个注解的作用是:满足SpringBootCondition
条件这个Bean才会被Spring容器管理 -
他的条件是
LinkedHashMapCondition
,继承了JetCacheCondition
,也就是说配置文件中配置了缓存类型为linkedhashmap
时这个类才会被Spring容器管理,才会完成LinkedHashMapCacheBuilder构造器的初始化 -
JetCacheCondition
逻辑并不复杂,可自行查看
CaffeineAutoConfiguration
com.alicp.jetcache.autoconfigure.CaffeineAutoConfiguration
继承了EmbeddedCacheAutoInit
,实现了initCache
方法,先通过CaffeineCacheBuilder创建一个默认实现类,然后解析相关配置至构造器中完成初始化,代码如下:
-
同样使用了
@Conditional
注解,这个注解的作用是:满足SpringBootCondition
条件这个Bean才会被Spring容器管理 -
他的条件是
CaffeineCondition
,继承了JetCacheCondition
,也就是说配置文件中配置了缓存类型为caffeine
时这个类才会被Spring容器管理,才会完成LinkedHashMapCacheBuilder构造器的初始化
ExternalCacheAutoInit
com.alicp.jetcache.autoconfigure.ExternalCacheAutoInit
抽象类继承了AbstractCacheAutoInit
,主要是覆盖父类的parseGeneralConfig
,解析远程缓存单有的配置keyPrefix
、valueEncoder
和valueDecoder
,代码如下:
RedisAutoInit
com.alicp.jetcache.autoconfigure.RedisAutoInit
继承了ExternalCacheAutoInit
,实现initCache
方法,完成了通过Jedis连接Redis的初始化操作,主要代码如下:
-
com.alicp.jetcache.autoconfigure.RedisAutoInit
是com.alicp.jetcache.autoconfigure.RedisAutoConfiguration
内部的静态类,在RedisAutoConfiguration内通过redisAutoInit()
方法定义RedisAutoInit作为Spring Bean -
同样RedisAutoConfiguration使用了
@Conditional
注解,满足SpringBootCondition
条件这个Bean才会被Spring容器管理,内部的RedisAutoInit也不会被管理,也就是说配置文件中配置了缓存类型为redis
时RedisLettuceAutoInit才会被Spring容器管理,才会完成RedisLettuceCacheBuilder构造器的初始化 -
实现了
initCache
方法- 先解析Redis的相关配置
- 通过Jedis创建Redis连接池
- 通过RedisCacheBuilder创建一个默认实现类
- 解析相关配置至构造器中完成初始化
- 将Redis连接保存至
AutoConfigureBeans
中
RedisLettuceAutoInit
com.alicp.jetcache.autoconfigure.RedisLettuceAutoInit
继承了ExternalCacheAutoInit
,实现initCache
方法,完成了通过Lettuce连接Redis的初始化操作,主要代码如下:
-
com.alicp.jetcache.autoconfigure.RedisLettuceAutoInit
是com.alicp.jetcache.autoconfigure.RedisLettuceAutoConfiguration
内部的静态类,在RedisLettuceAutoConfiguration内通过redisLettuceAutoInit()
方法定义RedisLettuceAutoInit作为Spring Bean -
同样RedisLettuceAutoConfiguration使用了
@Conditional
注解,满足SpringBootCondition
条件这个Bean才会被Spring容器管理,内部的RedisLettuceAutoInit也不会被管理,也就是说配置文件中配置了缓存类型为redis.lettuce
时RedisLettuceAutoInit才会被Spring容器管理,才会完成RedisLettuceCacheBuilder构造器的初始化 -
实现了
initCache
方法- 先解析Redis的相关配置
- 通过Lettuce创建Redis客户端和与Redis的连接
- 通过RedisLettuceCacheBuilder创建一个默认实现类
- 解析相关配置至构造器中完成初始化
- 获取
LettuceConnectionManager
管理器,将通过Lettuce创建Redis客户端和与Redis的连接保存 - 将Redis客户端、与Redis的连接、同步命令、异步命令和反应式命令相关保存至
AutoConfigureBeans
中
JetCacheAutoConfiguration自动配置
上面的初始化构造器的类需要被Spring容器管理,就需被扫描到,我们一般会设置扫描路径,但是别人引入JetCache肯定是作为其他包不能够被扫描到的,这些Bean也就不会被Spring管理,这里我们查看jetcache-autoconfigure
模块下src/main/resources/META-INF/spring.factories
文件,内容如下:
这应该是一种SPI
机制,这样这个项目以外的JetCache包里面的com.alicp.jetcache.autoconfigure.JetCacheAutoConfiguration
就会被Spring容器扫描到,我们来看看他的代码:
-
可以看到通过
@Import
注解,初始化构造器的那些类会被加入到Spring容器,加上@Condotional
注解,只有我们配置过的缓存类型的构造器才会被加入,然后保存至AutoConfigureBeans对象中 -
注意到这里我们注入的是
SpringConfigProvider
对象,加上@ConditionalOnMissingBean
注解,无法再次注册该对象至Spring容器,相比ConfigProvider
对象,它的区别是设置了EncoderParser为DefaultSpringEncoderParser,设置了KeyConvertorParser为DefaultSpringKeyConvertorParser,目的是支持两个解析器能够解析自定义bean -
在
BeanDependencyManager
中可以看到它是一个BeanFactoryPostProcessor
,用于BeanFactory容器初始后执行操作,目的是往JetCacheAutoConfiguration的BeanDefinition的依赖中添加几个AbstractCacheAutoInit类型的beanName,保证几个CacheBuilder构造器已经初始化 -
globalCacheConfig
方法中设置全局的相关配置并添加已经初始化的CacheBuilder构造器,然后返回GlobalCacheConfig让Spring容器管理,这样一来就完成了JetCache的解析配置并初始化的功能
CacheBuilder构造器
构造器的作用就是根据配置构建一个对应类型的缓存实例
CacheBuilder的子类结构如下:

根据类名就可以知道其作用
CacheBuilder接口只定义了一个buildCache()
方法,用于构建缓存实例,交由不同的实现类
AbstractCacheBuilder抽象类实现了buildCache()
方法,主要代码如下:
-
实现了
java.lang.Cloneable
的clone方法,支持克隆该对象,因为每个缓存实例的配置不一定相同,这个构造器中保存的是全局的一些配置,所以需要克隆一个构造器出来为每个缓存实例设置其自己的配置而不影响这个最初始的构造器 -
定义CacheConfig对象存放缓存配置,构建缓存实例需要根据这些配置
-
定义的
buildFunc
函数用于构建缓存实例,我们在初始化构造器中可以看到,不同的构造器设置的该函数都是new一个缓存实例并传入配置信息,例如:
不同类型的构造器区别在于CacheConfig类型不同,因为远程和本地的配置是有所区别的,还有就是设置的buildFunc
函数不同,因为需要构建不同的缓存实例,和上面的例子差不多,都是new一个缓存实例并传入配置信息,这里就不一一讲述了
AOP
主要查看jetcache-anno子模块,提供AOP功能
启用JetCache
JetCache可以通过@EnableMethodCache和@EnableCreateCacheAnnotation注解完成AOP的初始化工作,我们在Spring Boot工程中的启动类上面添加这两个注解即可启用JetCache缓存。
@EnableMethodCache
注解的相关配置在上面的'如何使用'中已经讲过了,这里我们关注@Import
注解中的CommonConfiguration
和ConfigSelector
两个类,将会被Spring容器管理
-
com.alicp.jetcache.anno.config.CommonConfiguration
上面有@Configuration注解,所以会被作为一个Spring Bean,里面定义了一个Bean为ConfigMap
,所以这个Bean也会被Spring容器管理,com.alicp.jetcache.anno.support.ConfigMap
中保存方法与缓存注解配置信息的映射关系 -
com.alicp.jetcache.anno.config.ConfigSelector
继承了AdviceModeImportSelector,通过@Import
注解他的selectImports
方法会被调用,根据不同的AdviceMode导入不同的配置类,可以看到会返回一个JetCacheProxyConfiguration类名称,那么它也会被注入
com.alicp.jetcache.anno.config.JetCacheProxyConfiguration
是配置AOP的配置类,代码如下:
因为JetCacheProxyConfiguration是通过@Import
注解注入的并且实现了ImportAware
接口,当被注入Bean的时候会先调用其setImportMetadata
方法(这里好像必须添加@Configuration注解,不然无法被Spring识别出来)获取到@EnableMethodCache
注解的元信息
其中定义了两个Bean:
com.alicp.jetcache.anno.aop.JetCacheInterceptor
:实现了aop中的MethodInterceptor方法拦截器,可用于aop拦截方法后执行相关处理
com.alicp.jetcache.anno.aop.CacheAdvisor
:
-
继承了
org.springframework.aop.support.AbstractBeanFactoryPointcutAdvisor
,将会作为一个AOP切面 -
设置了通知advice为JetCacheInterceptor,也就是说被拦截的方法都会进入JetCacheInterceptor,JetCacheInterceptor就作为JetCache的入口了
-
根据注解设置了需要扫描的包路径以及优先级,默认是最低优先级
-
CacheAdvisor实现了
org.springframework.aopPointcutAdvisor
接口的getPointcut()
方法,设置这个切面的切入点为com.alicp.jetcache.anno.aop.CachePointcut
-
从CachePointcut作为切入点
-
实现了
org.springframework.aop.ClassFilter
接口,用于判断哪些类需要被拦截 -
实现了
org.springframework.aop.MethodMatcher
接口,用于判断哪些类中的哪些方法会被拦截 -
在判断方法是否需要进入JetCache的JetCacheInterceptor过程中,会解析方法上面的JetCache相关缓存注解,将配置信息封装
com.alicp.jetcache.anno.methodCacheInvokeConfig
对象中,并把它保存至com.alicp.jetcache.anno.support.ConfigMap
对象中
-
总结:@EnableMethodCache注解主要就是生成一个AOP切面用于拦截带有缓存注解的方法
@EnableCreateCacheAnnotation
相比@EnableMethodCache注解,没有相关属性,同样会导入CommonConfiguration类
不同的是将导入com.alicp.jetcache.anno.field.CreateCacheAnnotationBeanPostProcessor
类,它继承了org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor
作为一个BeanPostProcessor,用于在Spring初始化bean的时候做一些操作
从代码中可以看到他的作用是:如果这个bean内部存在添加了带有@CreateCache
注解的字段(没有添加static),会将这个字段作为需要注入的对象,解析成 com.alicp.jetcache.anno.field.LazyInitCache
缓存实例
LazyInitCache的主要代码如下:
-
可以看到通过
@CreateCache
创建的缓存实例也可以添加@CacheRefresh
和@CachePenetrationProtect
注解 -
在AbstractCache抽象类的computeIfAbsentImpl方法中我们有讲到,如果缓存实例是ProxyCache类型,则会先调用其
getTargetCache()
方法获取缓存实例对象,所以LazyInitCache在第一次访问的时候才进行初始化,并根据缓存注解配置信息创建(存在则直接获取)一个缓存实例
总结:@EnableCreateCacheAnnotation注解主要是支持@CreateCache能够创建缓存实例
通过@EnableMethodCache
和@EnableCreateCacheAnnotation
两个注解,加上前面的解析配置过程
,已经完成的JetCache的解析与初始化过程,那么接下来我们来看看JetCache如何处理被拦截的方法。
拦截器
从com.alicp.jetcache.anno.aop.CachePointcut
切入点判断方法是否需要拦截的逻辑:
-
方法所在的类对象是否匹配,除去以"java"、"org.springframework"开头和包含"$$EnhancerBySpringCGLIB$$"、"$$FastClassBySpringCGLIB$$"的类,该类是否在我们通过
@EnableMethodCache
注解配置的basePackages中 -
从
ConfigMap
获取方法对应的CacheInvokeConfig
对象,也就是获取缓存配置信息- 如果是一个空对象,那么不需要被拦截,因为前面已经判断了所在的类是否需要被拦截,而这个类中并不是所有的方法都会添加缓存注解,所以这一类的方法会设置一个空对象(定义在CacheInvokeConfig内部的一个静态对象添加了final修饰),保存在ConfigMap中
- 如果不为null,则需被拦截
- 通过CacheConfigUtil解析这个方法的缓存注解,如果有@Cached注解或者@CacheInvalidate注解或者@CacheUpdate注解,先解析注解生成CacheInvokeConfig对象保存至ConfigMap中,然后该方法会被拦截,否在保存一个空对象不会被拦截
ConfigProvider
com.alicp.jetcache.anno.support.ConfigProvide
是一个配置提供者对象,包含了JetCache的全局配置、缓存实例管理器、缓存value转换器、缓存key转换器、上下文和监控指标相关信息,主要代码如下:
继承了com.alicp.jetcache.anno.support.AbstractLifecycle
,查看其代码可以看到有两个方法,分别为init()
初始化方法和shutdown()
销毁方法,因为分别添加了@PostConstruct
注解和@PreDestroy
注解,所以在Spring初始化时会调用init(),在Spring容器销毁时会调用shutdown()方法,内部分别调用doInit()和doShutdown(),这两个方法交由子类实现
在doInit()方法中先启动缓存指标监控器,用于周期性打印各项缓存指标,然后初始化CacheContext缓存上下文,SpringConfigProvider返回的是SpringConfigContext
在doShutdown()方法中关闭缓存指标监控器,清除缓存实例
CacheContext
com.alicp.jetcache.anno.support.CacheContext
缓存上下文主要为每一个被拦截的请求创建缓存上下文,构建对应的缓存实例,主要代码如下:
createCacheInvokeContext
方法返回一个本次调用的上下文CacheInvokeContext,为这个上下文设置缓存函数,用于获取或者构建缓存实例,这个函数在CacheHandler中会被调用,我们来看看这个函数的处理逻辑:有两个入参,分别为本次调用的上下文和缓存注解的配置信息
首先从缓存注解的配置信息中获取缓存实例,如果不为null则直接返回,否则调用createCacheByCachedConfig
方法,根据配置通过CacheBuilder构造器创建一个缓存实例对象
createCacheByCachedConfig
方法:
-
如果没有定义缓存实例名称(@Cached注解中的name配置),则生成
类名+方法名+(参数类型)
作为缓存实例名称 -
然后调用
__createOrGetCache
方法
__createOrGetCache
方法:
-
通过缓存实例管理器SimpleCacheManager根据缓存区域area和缓存实例名称cacheName获取缓存实例对象,如果不为null则直接返回,判断缓存实例对象是否为null为进行两次确认,第二次会给当前CacheContext加锁进行判断,避免线程不安全
-
缓存实例对象还是为null的话,先判断缓存区域area是否添加至缓存实例名称中,是的话"area_cacheName"为缓存实例名称,然后调用
buildCache
方法创建一个缓存实例对象
buildCache
方法:根据缓存实例类型构建不同的缓存实例对象,处理逻辑如下:
- CacheType为
LOCAL
则调用buildLocal
方法:
- CacheType为
REMOTE
则调用buildRemote
方法:
- CacheType为
BOTH
则调用buildLocal
方法构建本地缓存实例,调用buildRemote
方法构建远程缓存实例:
-
设置刷新策略RefreshPolicy,没有的话为null
-
将缓存实例对象封装成CacheHandlerRefreshCache对象,用于后续的添加刷新任务,在之前的'AbstractCache抽象类'有讲到
-
设置是否开启缓存未命中时加载方法的保护模式,全局默认为false
-
将缓存实例添加至监控管理器中
JetCacheInterceptor
被拦截后的处理在com.alicp.jetcache.anno.aop.JetCacheInterceptor
中,代码如下:
从ConfigMap
中获取被拦截的方法对象的缓存配置信息,如果没有则直接执行该方法,否则继续往下执行
根据CacheContext
对象(SpringCacheContext,因为在之前讲到的'JetCacheAutoConfiguration自动配置'中有说到注入的是SpringConfigProvider对象,在其初始化方法中调用newContext()方法生成SpringCacheContext)调用其createCacheInvokeContext
方法为本次调用创建一个上下文CacheInvokeContext
,并设置获取缓存实例函数,具体实现逻辑查看上面讲到的CacheContext
设置本次调用上下文的targetObject为被拦截对象,invoker为被拦截对象的调用器,method为被拦截方法,args为方法入参,cacheInvokeConfig为缓存配置信息,hiddenPackages为缓存实例名称需要截断的包名
通过CacheHandler的invoke方法继续往下执行
CacheHandler
com.alicp.jetcache.anno.method.CacheHandler
用于JetCache处理被拦截的方法,部分代码如下:
直接查看invokeWithCached
方法:
-
获取缓存注解信息
-
根据本地调用的上下文CacheInvokeContext获取缓存实例对象(调用其cacheFunction函数),在CacheContext中有讲到
-
如果缓存实例不存在则直接调用invokeOrigin方法,执行被拦截的对象的调用器
-
根据本次调用的上下文CacheInvokeContext生成缓存key,根据配置的缓存key的SpEL表达式生成,如果没有配置则返回入参对象,如果没有对象则返回"_ $JETCACHE_NULL_KEY$_"
-
根据配置condition表达式判断是否需要走缓存
-
创建一个
CacheLoader
对象,用于执行被拦截的对象的调用器,也就是加载原有方法 -
调用缓存实例的
computeIfAbsent(key, loader)
方法获取结果,这个方法的处理过程可查看'缓存API'这一小节
至此结束!!!😄😄😄
__EOF__

本文链接:https://www.cnblogs.com/lifullmoon/p/13854158.html
关于博主:本着学习与分享的目的,将持续不断的进行知识分享。望各位到访看客如有喜欢的文章,可以点击一下“推荐”,若有不同建议或者意见,也请不吝赐教,博主感激不尽。另外,欢迎转载博主的文章,请务必依据文章下方的版权声明转载。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?