likes
comments
collection
share

SpringBoot系列教程之内存Caffeine整合Cachebale注解

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

SpringBoot系列教程之内存Caffeine整合Cachebale注解

前面一篇文章虽说介绍了Caffeine的使用方式,但是更多的是偏向于基础的Caffeine用法;接下来这边博文将给大家介绍一下Caffeine结合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>

2. 配置

SpringBoot官方对Caffeine的集成,提供了非常好的支持,比如本文介绍的在使用 @Cacheable 注解来处理缓存时,我们无需额外操作,直接在配置文件来实现缓存的指定,以及对应的Caffeine相关配置限定

核心配置如下 application.yml

# 指定全局默认的缓存策略
spring:
  cache:
    type: caffeine
    caffeine:
      spec: initialCapacity=10,maximumSize=200,expireAfterWrite=5m

上面的 spring.cache.type 主要用来表明缓存注解的具体缓存实现为 Caffeine,当然还可以是Guava、redis等

其次就是 spring.cache.caffeine.spec, 它指定了Caffeine的初始化容量大小,最大个数,失效时间等 (无特殊场景时,所有的缓存注解都是公用这个配置的)

使用实例

1. 开启缓存注解支持

首先在启动类上添加 @EnableCaching 注解,注意若不加则缓存不会生效

@EnableCaching
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class);
    }
}

2. 使用实例

我们定义一个UserService,主要是用来操作用户相关信息,现在先定义一个User实体类

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private Integer uid;
    private String uname;
}

然后添加增删查

@Service
// 这个注释的是默认的缓存策略,此时对应的 cacheManager 由 spring.cache.caffeine.spec 来指定缓存规则
@CacheConfig(cacheNames = "customCache")
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);
    }

}

上面分别介绍了三个注解

  • CachePut: 不管缓存有没有,都将方法的返回结果写入缓存中
  • Cacheable: 先从缓存查,没有则执行方法,并塞入缓存
  • CacheEvit: 失效缓存

其次在类上还有一个@CacheConfig注解,主要定义了一个 cacheNames 属性,当我们使用缓存注解时,需要注意的是这个cacheNames必须得有,否则就会报错

当一个类中所有缓存公用一个cacheNames时,可以直接在类上添加@CacheConfig来避免在每个地方都添加指定

3. 写个测试demo

@RestController
public class TestController {

    @Autowired
    private AnoCacheService anoCacheService;
    private AtomicInteger uid = new AtomicInteger(1);

    @RequestMapping(path = "save")
    public User save(String name) {
        return anoCacheService.saveUser(new User(uid.getAndAdd(1), name));
    }

    @RequestMapping(path = "query")
    public User query(int userId) {
        User user = anoCacheService.getUser(userId);
        return user == null ? new User() : user;
    }

    @RequestMapping(path = "remove")
    public String remove(int userId) {
        anoCacheService.removeUser(userId);
        return "ok";
    }
}

我们来实际看一下,第一次没有数据时,返回的是不是空;当有数据之后,缓存是否会命中

4. 小结

这篇博文主要介绍了SpringBoot如何整合Caffeine,结合Spring的缓存注解,基于可以说是很低成本的就让我们的方法实现缓存功能,但是请注意,有几个注意点

  1. 当我并不希望所有数据公用一个缓存时,怎么处理?
  • 比如我有一些关键数据,虽然访问频率可能没那么高,但是还每次实际读取的成本很高,又不怎么变动,我希望可以更长久的缓存;
  • 如果公用一个缓存,则有可能导致它们被其他的热点数据给挤下线了(超过最大数量限制给删除了)
  1. 在实际使用时,需要特别注意,加了缓存注解之后,返回的实际上是缓存中的对象,如上面返回的是User对象还好,如果返回的是一个容器,那么直接像这些容器中进行额外的添加、删除元素,是直接影响缓存结果的

另外,查看本文推荐结合下面几篇博文一起享用,以获取更多的知识点

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

0. 项目

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

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

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