SpringBoot+Caffeine实现本地缓存(文末附源码)

为什么用Caffeine做本地缓存

Caffeine是基于Java 8的高性能缓存库,可提供接近最佳的命中率。

咖啡因提供了灵活的构造来创建具有以下功能组合的缓存:

  1. 自动将条目自动加载到缓存中,可以选择异步加载
  2. 基于频率和新近度超过最大值时基于大小的逐出
  3. 自上次访问或上次写入以来测得的基于时间的条目到期
  4. 发生第一个陈旧的条目请求时,异步刷新
  5. 键自动包装在弱引用中
  6. 值自动包装在弱引用或软引用中
  7. 逐出(或以其他方式删除)条目的通知
  8. 写入传播到外部资源
  9. 缓存访问统计信息的累积

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值