为什么用Caffeine做本地缓存
Caffeine是基于Java 8的高性能缓存库,可提供接近最佳的命中率。
咖啡因提供了灵活的构造来创建具有以下功能组合的缓存:
- 自动将条目自动加载到缓存中,可以选择异步加载
- 基于频率和新近度超过最大值时基于大小的逐出
- 自上次访问或上次写入以来测得的基于时间的条目到期
- 发生第一个陈旧的条目请求时,异步刷新
- 键自动包装在弱引用中
- 值自动包装在弱引用或软引用中
- 逐出(或以其他方式删除)条目的通知
- 写入传播到外部资源
- 缓存访问统计信息的累积
Spring Boot原生支持多种缓存
package org.springframework.boot.autoconfigure.cache;
public enum CacheType {
GENERIC,
JCACHE,
HAZELCAST,
COUCHBASE,
INFINISPAN,
REDIS,
CACHE2K,
CAFFEINE,
SIMPLE,
NONE;
private CacheType() {
}
}
我们从以上Spring Boot源码可以看出,Spring Boot框架支持多种缓存,包括本地缓存,以及redis等分布式缓存。
Caffeine实现了Spring Boot中的CacheManager接口,我们只需要在配置中告诉Spring Boot我们使用的缓存是Caffeine,Spring Boot自动会使用CacheManager帮我们管理缓存。具体的原理大家自己去看,今天我教大家如何实操。
依赖导入
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.8</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
构建项目
启动类配配置
@SpringBootApplication
@EnableCaching //显式开启cache缓存
public class CodeApplication {
public static void main(String[] args) {
SpringApplication.run(CodeApplication.class, args);
}
}
自定义Cache类型
@AllArgsConstructor
@Getter
public enum CacheType {
/*
新增缓存方法步骤:
1. 在CacheType中新增缓存类型 (会自动创建缓存器) --> 也可以不定义新的缓存器 共用已经存在的缓存器 但要注意key的生成方式 避免key冲突
2. 在需要缓存的方法上加入 @Cacheable注解 并给@Cacheable注解 设置步骤1中的缓存名称 及 指定缓存key生成方式
缓存具体执行步骤:
1.会先根据key去缓存中找缓存
2.若找到缓存 则直接返回缓存中的数据 不会执行@Cacheable注解方法下的代码
若没有找到缓存 则会执行方法中的代码 然后将方法的返回结果进行缓存 key为@Cacheable注解中的key指定方式生成 value为方法的返回值
提示:
1. 若在同一个类中调用缓存方法 请使用AOP代理 否则无法使用缓存
2. @Cacheable: 若缓存存在,则使用缓存;不存在,则执行方法,并将结果存入缓存
@CacheEvit: 失效缓存(等于删除缓存)
@CachePut: 更新缓存(不管缓存存不存在,都会执行方法,然后更新缓存)
*/
//将过期时间设置为30s,方便我们测试
USER_CACHE("用户缓存", "userCache", 30, TimeUnit.SECONDS, 500, 1000);
/**
* 描述
*/
private String description;
/**
* 缓存名称
*/
private String cacheName;
/**
* 过期时间
*/
private long expireTime;
/**
* 时间单位
*/
private TimeUnit timeUnit;
/**
* 初始容量
*/
private int initialCapacity;
/**
* 最大缓存数量
*/
private long maximumSize;
}
注入Caffeine配置
@Configuration
public class Config {
@Bean
public CacheManager caffeineCacheManager() {
SimpleCacheManager cacheManager = new SimpleCacheManager();
List<CaffeineCache> caffeines = new ArrayList<CaffeineCache>();
for (CacheType type : CacheType.values()) {
CaffeineCache caffeineCache = new CaffeineCache(type.getCacheName(),
Caffeine.newBuilder()
//初始容量
.initialCapacity(type.getInitialCapacity())
//最大容量
.maximumSize(type.getMaximumSize())
//过期时间
.expireAfterWrite(type.getExpireTime(), type.getTimeUnit()).build());;
caffeines.add(caffeineCache);
}
cacheManager.setCaches(caffeines);
return cacheManager;
}
}
Entity
@Data
@Builder
public class User {
private String name;
private Integer age;
}
Controller
@RestController
@RequestMapping
public class Controller {
@Resource
private UserService userService;
/**
* @param id id
* @return {@link User }
* @Description 演示获取用户并进行缓存
* @Date 2023/04/02 22:16
*/
@GetMapping("getUser")
public User getUser(@RequestParam Integer id) {
//循环10次,看控制台会不会打印十次user信息
for (int i = 0; i < 10; i++) {
User user = userService.getUser(id);
}
return userService.getUser(id);
}
/**
* @param id id
* @return {@link User }
* @Description 观看缓存
* @Author mouyang
* @Date 2023/04/02 22:17
*/
@GetMapping("watchCache")
public User watchCache(@RequestParam Integer id) {
return userService.watchCache(id);
}
}
Service
public interface UserService {
User getUser(Integer id);
User watchCache(Integer id);
}
ServiceImpl
@Service
public class UserServiceImpl implements UserService {
@Resource
private CacheManager cacheManager;
@Override
@Cacheable(cacheNames = "userCache", key = "#id")
public User getUser(Integer id) {
User user = User.builder()
.name("张三")
.age(23)
.build();
System.out.println("获得user: " + user);
return user;
}
@Override
public User watchCache(Integer id) {
Cache cache = cacheManager.getCache(CacheType.USER_CACHE.getCacheName());
Cache.ValueWrapper valueWrapper = cache.get(id);
User user = (User) valueWrapper.get();
System.out.println("从缓存中获取到id为+" + id + "的+user:" + user);
return user;
}
}
启动项目,开启测试
调用缓存方法测试
GET http://localhost:8080/getUser?id =1
首先我们多次调用以上链接,看看控制台是否会重复打印user信息
输出结果
获得user: User(name=张三, age=23)
user信息只输出了一次,说们只有在我们第一次调用该方法时,才执行了获取user的方法,后面都是从缓存中拿的。
查看缓存测试
我们可以尝试调用以下链接查看缓存中是否有user(当然,我们调用该方法必须在缓存有效期内,我这里设置的是30s)
GET http://localhost:8080/watchCache?id=1
输出结果
从缓存中获取到id为+1的+user:User(name=张三, age=23)
等待user超过我们设定的缓存有效期过后,我们尝试再次调用该链接,查看缓存中是否还有相关缓存
java.lang.NullPointerException: null
at com.mouyang.study.service.impl.UserServiceImpl.watchCache(UserServiceImpl.java:33) ~[classes/:na]
at com.mouyang.study.service.impl.UserServiceImpl$$FastClassBySpringCGLIB$$f2284fc1.invoke(<generated>) ~[classes/:na]
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) ~[spring-core-5.3.25.jar:5.3.25]
at org.springframework.aop.framework.CglibAopProxy.invokeMethod(CglibAopProxy.java:386) ~[spring-aop-5.3.25.jar:5.3.25]
at org.springframework.aop.framework.CglibAopProxy.access$000(CglibAopProxy.java:85) ~[spring-aop-5.3.25.jar:5.3.25]
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:704) ~[spring-aop-5.3.25.jar:5.3.25]
我们看到,这时报了空指针异常,说明缓存中id=1的缓存已经失效
好了,以上是Spring Boot整合Caffeine的实操教学,希望大家可以多多研习。
附源码地址 点我跳转
https://gitee.com/young-mou/study/tree/master/SpringBoot+Caffeine