likes
comments
collection
share

SpringBoot系列教程之内存缓存Caffiene自定义CacheManager

作者站长头像
站长
· 阅读数 13

内存缓存Caffiene自定义CacheManager | 一灰灰Blog

上一篇介绍了Caffiene整合Spring的缓存注解@Cacheable,在这篇示例中,所有的缓存公用,但是实际的场景中,我们可能会更希望针对不同的场景,配置不同的缓存(比如我的关键数据,虽然访问频率可能没那么高,但是每次实际读取的成本很高,又不怎么变动,我希望可以更长久的缓存;不希望这些数据因为缓存的淘汰策略被其他的热点数据给淘汰掉),那么可以怎么处理呢?

接下来我们来看一下两种不同的方式,来实现上面的诉求

项目配置

1. 依赖

首先搭建一个标准的SpringBoot项目工程,相关版本以及依赖如下

本项目借助SpringBoot 2.2.1.RELEASE + maven 3.5.3 + IDEA进行开发

<dependencies>
    <dependency>
        <groupId>com.github.ben-manes.caffeine</groupId>
        <artifactId>caffeine</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-cache</artifactId>
    </dependency>
</dependencies>

与前面不同,我们不需要在配置文件中指定缓存类型以及caffeine的相关条件参数,直接放在配置类中

2. 配置类

@Configuration
public class CacheConfig {

    @Primary
    @Bean("customCacheManager")
    public CacheManager customCacheManager() {
        SimpleCacheManager simpleCacheManager = new SimpleCacheManager();
        List<Cache> cacheList = new ArrayList<>();
        cacheList.add(customCache());
        simpleCacheManager.setCaches(cacheList);
        return simpleCacheManager;
    }

    public Cache customCache() {
        return new CaffeineCache("customCache", Caffeine.newBuilder()
                .maximumSize(200)
                .initialCapacity(100)
                .expireAfterWrite(5, TimeUnit.MINUTES)
                .recordStats()
                .build(),
                true);
    }
}

注意上面的 cacheList,其中传入的就是Cache对象,每个Cache对象就可以理解为一个缓存实例,重点注意构造参数中的第一个customCache,这个就是后面缓存具体使用时,注解中的cacheNames属性

使用实例

1. SimpleCacheManager 使用实例

@Service
@CacheConfig(cacheNames = "customCache", cacheManager = "customCacheManager")
public class AnoCacheService {

    /**
     * 用一个map来模拟存储
     */
    private Map<Integer, User> userDb = new ConcurrentHashMap<>();

    /**
     * 添加数据,并保存到缓存中, 不管缓存中有没有,都会更新缓存
     *
     * @param user
     */
    @CachePut(key = "#user.uid")
    public User saveUser(User user) {
        userDb.put(user.getUid(), user);
        return user;
    }

    /**
     * 优先从缓存中获取数据,若不存在,则从 userDb 中查询,并会将结果写入到缓存中
     *
     * @param userId
     * @return
     */
    @Cacheable(key = "#userId")
    public User getUser(int userId) {
        System.out.println("doGetUser from DB:" + userId);
        return userDb.get(userId);
    }

    @CacheEvict(key = "#userId")
    public void removeUser(int userId) {
        userDb.remove(userId);
    }

}

重点注意一下上面的@CacheConfig,它定义了这个类中的的缓存,都使用 customCacheManager 缓存管理器,且具体的缓存为定义的customCache (改成其他的会报错)

从上面的配置声明,也可以看出,当我们希望使用多个缓存时,可以直接如下面这种方式进行扩展即可

@Primary
@Bean("customCacheManager")
public CacheManager customCacheManager() {
    SimpleCacheManager simpleCacheManager = new SimpleCacheManager();
    List<Cache> cacheList = new ArrayList<>();
    cacheList.add(customCache());
    cacheList.add(customCache2());
    simpleCacheManager.setCaches(cacheList);
    return simpleCacheManager;
}

public Cache customCache() {
    return new CaffeineCache("customCache", Caffeine.newBuilder()
            .maximumSize(200)
            .initialCapacity(100)
            .expireAfterWrite(5, TimeUnit.MINUTES)
            .recordStats()
            .build(),
            true);
}

public Cache customCache2() {
    return new CaffeineCache("customCache2", Caffeine.newBuilder()
            .maximumSize(100)
            .initialCapacity(10)
            .expireAfterWrite(30, TimeUnit.MINUTES)
            .recordStats()
            .build(),
            true);
}

2. CaffeineCacheManager 方式

除了上面这种方式之外,我们当然也可以再额外定义一个CacheManager,如下

@Bean("otherCacheManager")
public CacheManager cacheManager() {
    CaffeineCacheManager cacheManager = new CaffeineCacheManager();
    cacheManager.setCaffeine(Caffeine.newBuilder()
            // 设置过期时间,写入后五分钟国企
            .expireAfterWrite(5, TimeUnit.MINUTES)
            // 初始化缓存空间大小
            .initialCapacity(100)
            // 最大的缓存条数
            .maximumSize(200));
    return cacheManager;
}

使用上面这种方式,cacheName可以不需要指定,具体使用如下

/**
 * 1. cacheManager 指定具体的缓存管理器
 * 2. cacheName 表示这个缓存前缀
 * 3. 通过CacheConfig 注解进行修饰,表示适用于这个类下的所有公共方法
 *
 * @author YiHui
 * @date 2023/3/5
 */
@Service
@CacheConfig(cacheNames = "ano2", cacheManager = "otherCacheManager")
public class AnoCacheService2 {

    /**
     * 用一个map来模拟存储
     */
    private Map<Integer, User> userDb = new ConcurrentHashMap<>();

    /**
     * 添加数据,并保存到缓存中, 不管缓存中有没有,都会更新缓存
     *
     * @param user
     */
    @CachePut(key = "#user.uid")
    public User saveUser(User user) {
        userDb.put(user.getUid(), user);
        return user;
    }

    /**
     * 优先从缓存中获取数据,若不存在,则从 userDb 中查询,并会将结果写入到缓存中
     *
     * @param userId
     * @return
     */
    @Cacheable(key = "#userId")
    public User getUser(int userId) {
        System.out.println("doGetUser from DB:" + userId);
        return userDb.get(userId);
    }

    @CacheEvict(key = "#userId")
    public void removeUser(int userId) {
        userDb.remove(userId);
    }

}

方法的内部实现完全一致;重点看@CacheConfig中的属性值

  • cacheNames 表示这个缓存前缀,没有约束限制

3. 测试

上面介绍了两种使用不同缓存的姿势:

  • SimpleCacheManager: 定义多个Cache
  • 多个CacheManager

我们写个简单的验证上面两个CacheManager表示不同缓存的测试用例

@RestController
public class TestController {

    @Autowired
    private AnoCacheService anoCacheService;

    @Autowired
    private AnoCacheService2 anoCacheService2;

    private AtomicInteger uid = new AtomicInteger(1);
    private AtomicInteger uid2 = new AtomicInteger(1);

    @RequestMapping(path = "save")
    public User save(String name,
                     @RequestParam(required = false, defaultValue = "1") Integer type) {
        if (type == 1) {
            return anoCacheService.saveUser(new User(uid.getAndAdd(1), name));
        } else {
            return anoCacheService2.saveUser(new User(uid2.getAndAdd(1), name));
        }
    }

    @RequestMapping(path = "query")
    public User query(int userId, @RequestParam(required = false, defaultValue = "1") Integer type) {
        User user = type == 1 ? anoCacheService.getUser(userId) : anoCacheService2.getUser(userId);
        return user == null ? new User() : user;
    }

    @RequestMapping(path = "remove")
    public String remove(int userId, @RequestParam(required = false, defaultValue = "1") Integer type) {
        if (type == 1) anoCacheService.removeUser(userId);
        else anoCacheService2.removeUser(userId);
        return "ok";
    }

}

操作步骤:

  • anoCacheService 写入缓存
  • anoCacheService2 查看缓存,此时不应该能查到前面写入的缓存

SpringBoot系列教程之内存缓存Caffiene自定义CacheManager

不能错过的源码和相关知识点

0. 项目

系列博文:

1. 微信公众号: 一灰灰Blog

尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激

下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛