springboot+caffeine实现本地缓存
1.引入依赖
<properties>
<caffeine.cache.version>2.7.0</caffeine.cache.version>
</properties>
<dependencies>
<!-- Spring boot Cache-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!--for caffeine cache-->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>${caffeine.cache.version}</version>
</dependency>
</dependencies>
2.configuration,配置(可以写代码配置,也可以在配置文件设置)
package com.coding.controller; import com.github.benmanes.caffeine.cache.Caffeine; import org.springframework.cache.CacheManager; import org.springframework.cache.caffeine.CaffeineCacheManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.concurrent.TimeUnit; @Configuration public class CacheConfig { @Bean public CacheManager cacheManager() { CaffeineCacheManager cacheManager = new CaffeineCacheManager(); cacheManager.setCaffeine(Caffeine.newBuilder() // 设置最后一次写入后经过固定时间过期 .expireAfterWrite(2, TimeUnit.SECONDS) // 初始的缓存空间大小 .initialCapacity(100) // 缓存的最大条数 .maximumSize(1000)); return cacheManager; } }
或者
spring.cache.caffeine.spec=initialCapacity=50,maximumSize=500,expireAfterWrite=5s
spring.cache.type=caffeine
3.使用@EnableCaching注解让Spring Boot开启对缓存的支持
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class) @EnableCaching public class AliyunOssApplication { public static void main(String[] args) { SpringApplication.run(AliyunOssApplication.class,args); } }
Caffeine配置说明:
initialCapacity=[integer]: 初始的缓存空间大小 maximumSize=[long]: 缓存的最大条数 maximumWeight=[long]: 缓存的最大权重 expireAfterAccess=[duration]: 最后一次写入或访问后经过固定时间过期 expireAfterWrite=[duration]: 最后一次写入后经过固定时间过期 refreshAfterWrite=[duration]: 创建缓存或者最近一次更新缓存后经过固定的时间间隔,刷新缓存 weakKeys: 打开key的弱引用 weakValues:打开value的弱引用 softValues:打开value的软引用 recordStats:开发统计功能
注意:
- expireAfterWrite和expireAfterAccess同事存在时,以expireAfterWrite为准。
- maximumSize和maximumWeight不可以同时使用
- weakValues和softValues不可以同时使用
4.使用
package com.coding.controller; import lombok.extern.slf4j.Slf4j; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.CacheConfig; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CachePut; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import javax.annotation.Resource; import java.util.Collection; import java.util.HashMap; @Slf4j @Service @CacheConfig(cacheNames = "cacheManager") public class UserInfoServiceImpl implements UserInfoService { @Resource private CacheManager cacheManager; /** * 模拟数据库存储数据 */ private HashMap<Integer, UserInfo> userInfoMap = new HashMap<Integer, UserInfo>() { { put(1, new UserInfo()); } }; @Override @CachePut(key = "#userInfo.id") public void addUserInfo(UserInfo userInfo) { log.info("create"); userInfoMap.put(userInfo.getId(), userInfo); } @Override @Cacheable(cacheNames = "getByName") public UserInfo getByName(Integer id) { log.info("get"); Collection<String> cacheNames = cacheManager.getCacheNames(); log.info(cacheNames.toString()); return userInfoMap.get(id); } @Override @CachePut(key = "#userInfo.id") public UserInfo updateUserInfo(UserInfo userInfo) { log.info("update"); if (!userInfoMap.containsKey(userInfo.getId())) { return null; } // 取旧的值 UserInfo oldUserInfo = userInfoMap.get(userInfo.getId()); // 替换内容 if (!StringUtils.isEmpty(oldUserInfo.getAge())) { oldUserInfo.setAge(userInfo.getAge()); } if (!StringUtils.isEmpty(oldUserInfo.getName())) { oldUserInfo.setName(userInfo.getName()); } if (!StringUtils.isEmpty(oldUserInfo.getSex())) { oldUserInfo.setSex(userInfo.getSex()); } // 将新的对象存储,更新旧对象信息 userInfoMap.put(oldUserInfo.getId(), oldUserInfo); // 返回新对象信息 return oldUserInfo; } @Override @CacheEvict(key = "#id") public void deleteById(Integer id) { log.info("delete"); userInfoMap.remove(id); } }
附:spring cache相关注解介绍 @Cacheable、@CachePut、@CacheEvict
@Cacheable
应用到读取数据的方法上,即可缓存的方法,如查找方法,先从缓存中读取,如果没有再调用相应方法获取数据,然后把数据添加到缓存中。
该注解主要有下面几个参数:
- value、cacheNames:两个等同的参数(cacheNames为Spring 4新增,作为value的别名),用于指定缓存存储的集合名。由于Spring 4中新增了@CacheConfig,因此在Spring 3中原本必须有的value属性,也成为非必需项了
- key:缓存对象存储在Map集合中的key值,非必需,缺省按照函数的所有参数组合作为key值,若自己配置需使用SpEL表达式,比如:@Cacheable(key = "#p0"):使用函数第一个参数作为缓存的key值,更多关于SpEL表达式的详细内容可参考官方文档
- condition:缓存对象的条件,非必需,也需使用SpEL表达式,只有满足表达式条件的内容才会被缓存,比如:@Cacheable(key = "#p0", condition = "#p0.length() < 3"),表示只有当第一个参数的长度小于3的时候才会被缓存。
- unless:另外一个缓存条件参数,非必需,需使用SpEL表达式。它不同于condition参数的地方在于它的判断时机,该条件是在函数被调用之后才做判断的,所以它可以通过对result进行判断。
- keyGenerator:用于指定key生成器,非必需。若需要指定一个自定义的key生成器,我们需要去实现org.springframework.cache.interceptor.KeyGenerator接口,并使用该参数来指定。需要注意的是:该参数与key是互斥的
- cacheManager:用于指定使用哪个缓存管理器,非必需。只有当有多个时才需要使用
- cacheResolver:用于指定使用那个缓存解析器,非必需。需通过org.springframework.cache.interceptor.CacheResolver接口来实现自己的缓存解析器,并用该参数指定。
@Cacheable是用来声明方法是可缓存的。将结果存储到缓存中以便后续使用相同参数调用时不需执行实际的方法。直接从缓存中取值。最简单的格式需要制定缓存名称。
例如:
1 @Cacheable("books") 2 public Book findBook(ISBN isbn) {...}
在上面的代码片段中,findBook方法与名为books的缓存想关联。每次调用该方法时,将在缓存中检查该请求是否已执行,以免重复执行。虽然在大多数情况下,只有一个缓存被声明,注释允许指定多个名称,以便使用多个缓存。这种情况下,在执行方法之前,每个缓存都会检查之前执行的方法,只要有一个缓存命中,即直接从缓存中返回相关的值。
即使没有实际执行缓存方法,所有其他不包含该值的缓存也将被更新。
例如:
@Cacheable({"books", "isbns"}) public Book findBook(ISBN isbn) {...}
默认key生成:
默认key的生成按照以下规则:
- 如果没有参数,则使用0作为key
- 如果只有一个参数,使用该参数作为key
- 如果又多个参数,使用包含所有参数的hashCode作为key
自定义key的生成:
当目标方法参数有多个时,有些参数并不适合缓存逻辑
比如:
@Cacheable("books") public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
其中checkWarehouse,includeUsed并不适合当做缓存的key.针对这种情况,Cacheable 允许指定生成key的关键属性,并且支持支持SpringEL表达式。(推荐方法)
再看一些例子:
@Cacheable(cacheNames="books", key="#isbn") public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed) @Cacheable(cacheNames="books", key="#isbn.rawNumber") public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed) @Cacheable(cacheNames="books", key="T(someType).hash(#isbn)") public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed) @Cacheable(cacheNames="books", key="#map['bookid'].toString()") public Book findBook(Map<String, Object> map)
缓存的同步 sync:
在多线程环境下,某些操作可能使用相同参数同步调用。默认情况下,缓存不锁定任何资源,可能导致多次计算,而违反了缓存的目的。对于这些特定的情况,属性 sync 可以指示底层将缓存锁住,使只有一个线程可以进入计算,而其他线程堵塞,直到返回结果更新到缓存中。
例:
@Cacheable(cacheNames="foos", sync="true") public Foo executeExpensiveOperation(String id) {...}
属性condition:
有时候,一个方法可能不适合一直缓存(例如:可能依赖于给定的参数)。属性condition支持这种功能,通过SpEL 表达式来指定可求值的boolean值,为true才会缓存(在方法执行之前进行评估)。
例:
@Cacheable(cacheNames="book", condition="#name.length < 32") public Book findBook(String name)
此外,还有一个unless 属性可以用来是决定是否添加到缓存。与condition不同的是,unless表达式是在方法调用之后进行评估的。如果返回false,才放入缓存(与condition相反)。 #result指返回值 例:
@Cacheable(cacheNames="book", condition="#name.length < 32", unless="#result.name.length > 5"")
public Book findBook(String name)
@CachePut
如果缓存需要更新,且不干扰方法的执行,可以使用注解@CachePut。@CachePut标注的方法在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中。
@CachePut(cacheNames="book", key="#isbn") public Book updateBook(ISBN isbn, BookDescriptor descriptor)
注意:应该避免@CachePut 和 @Cacheable同时使用的情况。
@CacheEvict
spring cache不仅支持将数据缓存,还支持将缓存数据删除。此过程经常用于从缓存中清除过期或未使用的数据。
除了同@Cacheable一样的参数之外,@CacheEvict还有下面两个参数:
- allEntries:非必需,默认为false。当为true时,会移除所有数据
- beforeInvocation:非必需,默认为false,会在调用方法之后移除数据。当为true时,会在调用方法之前移除数据。
@CacheEvict要求指定一个或多个缓存,使之都受影响。此外,还提供了一个额外的参数allEntries 。表示是否需要清除缓存中的所有元素。默认为false,表示不需要。当指定了allEntries为true时,Spring Cache将忽略指定的key。有的时候我们需要Cache一下清除所有的元素。
@CacheEvict(cacheNames="books", allEntries=true) public void loadBooks(InputStream batch)
清除操作默认是在对应方法成功执行之后触发的,即方法如果因为抛出异常而未能成功返回时也不会触发清除操作。使用beforeInvocation可以改变触发清除操作的时间,当我们指定该属性值为true时,Spring会在调用该方法之前清除缓存中的指定元素
@CacheEvict(cacheNames="books", beforeInvocation=true) public void loadBooks(InputStream batch)
@CacheConfig
有时候一个类中可能会有多个缓存操作,而这些缓存操作可能是重复的。这个时候可以使用@CacheConfig
@CacheConfig("books") public class BookRepositoryImpl implements BookRepository { @Cacheable public Book findBook(ISBN isbn) {...} }
@CacheConfig是一个类级别的注解,允许共享缓存的名称、KeyGenerator、CacheManager 和CacheResolver。
该操作会被覆盖。
@Caching
组合多个Cache注解使用。示例:
@Caching( put = { @CachePut(value = "user", key = "#user.id"), @CachePut(value = "user", key = "#user.username"), @CachePut(value = "user", key = "#user.age") } }
缓存策略
如果缓存满了,从缓存中移除数据的策略,常见的有FIFO, LRU 、LFU
- FIFO (First in First Out) 先进先出策略,即先放入缓存的数据先被移除
- LRU (Least Recently Used) 最久未使用策略, 即使用时间距离现在最久的那个数据被移除
- LFU (Least Frequently Used) 最少使用策略,即一定时间内使用次数(频率)最少的那个数据被移除
- TTL(Time To Live)存活期,即从缓存中创建时间点开始至到期的一个时间段(不管在这个时间段内有没被访问过都将过期)
- TTI (Time To Idle)空闲期,即一个数据多久没有被访问就从缓存中移除的时间。
附注
通过@EnableCaching注解自动化配置合适的缓存管理器(CacheManager),Spring Boot根据下面的顺序去侦测缓存提供者:
- Generic
- JCache (JSR-107)
- EhCache 2.x
- Hazelcast
- Infinispan
- Redis
- Guava
- Simple
spring.cache.type = xxx
另外可通过注入cacheManager来调试查看使用哪种类型,进一步熟悉cache
@Resource
private CacheManager cacheManager;
【推荐】国内首个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速度为什么快?