真正的缓存之王,Google Guava 只是弟弟(二)

简介: 真正的缓存之王,Google Guava 只是弟弟(二)

3. SpringBoot 中默认Cache-Caffine Cache

SpringBoot 1.x版本中的默认本地cache是Guava Cache。在2.x(Spring Boot 2.0(spring 5) )版本中已经用Caffine Cache取代了Guava Cache。毕竟有了更优的缓存淘汰策略。

下面我们来说在SpringBoot2.x版本中如何使用cache。

1. 引入依赖:

<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.6.2</version>
</dependency>

2. 添加注解开启缓存支持

添加@EnableCaching注解:

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

3. 配置文件的方式注入相关参数

properties文件

spring.cache.cache-names=cache1
spring.cache.caffeine.spec=initialCapacity=50,maximumSize=500,expireAfterWrite=10s

或Yaml文件

spring:
  cache:
    type: caffeine
    cache-names:
    - userCache
    caffeine:
      spec: maximumSize=1024,refreshAfterWrite=60s

如果使用refreshAfterWrite配置,必须指定一个CacheLoader.不用该配置则无需这个bean,如上所述,该CacheLoader将关联被该缓存管理器管理的所有缓存,所以必须定义为CacheLoader<Object, Object>,自动配置将忽略所有泛型类型。

import com.github.benmanes.caffeine.cache.CacheLoader;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
 * @author: rickiyang
 * @date: 2019/6/15
 * @description:
 */
@Configuration
public class CacheConfig {
    /**
     * 相当于在构建LoadingCache对象的时候 build()方法中指定过期之后的加载策略方法
     * 必须要指定这个Bean,refreshAfterWrite=60s属性才生效
     * @return
     */
    @Bean
    public CacheLoader<String, Object> cacheLoader() {
        CacheLoader<String, Object> cacheLoader = new CacheLoader<String, Object>() {
            @Override
            public Object load(String key) throws Exception {
                return null;
            }
            // 重写这个方法将oldValue值返回回去,进而刷新缓存
            @Override
            public Object reload(String key, Object oldValue) throws Exception {
                return oldValue;
            }
        };
        return cacheLoader;
    }
}

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不可以同时使用

需要说明的是,使用配置文件的方式来进行缓存项配置,一般情况能满足使用需求,但是灵活性不是很高,如果我们有很多缓存项的情况下写起来会导致配置文件很长。所以一般情况下你也可以选择使用bean的方式来初始化Cache实例。

下面的演示使用bean的方式来注入:

package com.rickiyang.learn.cache;
import com.github.benmanes.caffeine.cache.CacheLoader;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.apache.commons.compress.utils.Lists;
import org.springframework.cache.CacheManager;
import org.springframework.cache.caffeine.CaffeineCache;
import org.springframework.cache.support.SimpleCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
 * @author: rickiyang
 * @date: 2019/6/15
 * @description:
 */
@Configuration
public class CacheConfig {
    /**
     * 创建基于Caffeine的Cache Manager
     * 初始化一些key存入
     * @return
     */
    @Bean
    @Primary
    public CacheManager caffeineCacheManager() {
        SimpleCacheManager cacheManager = new SimpleCacheManager();
        ArrayList<CaffeineCache> caches = Lists.newArrayList();
        List<CacheBean> list = setCacheBean();
        for(CacheBean cacheBean : list){
            caches.add(new CaffeineCache(cacheBean.getKey(),
                    Caffeine.newBuilder().recordStats()
                            .expireAfterWrite(cacheBean.getTtl(), TimeUnit.SECONDS)
                            .maximumSize(cacheBean.getMaximumSize())
                            .build()));
        }
        cacheManager.setCaches(caches);
        return cacheManager;
    }
    /**
     * 初始化一些缓存的 key
     * @return
     */
    private List<CacheBean> setCacheBean(){
        List<CacheBean> list = Lists.newArrayList();
        CacheBean userCache = new CacheBean();
        userCache.setKey("userCache");
        userCache.setTtl(60);
        userCache.setMaximumSize(10000);
        CacheBean deptCache = new CacheBean();
        deptCache.setKey("userCache");
        deptCache.setTtl(60);
        deptCache.setMaximumSize(10000);
        list.add(userCache);
        list.add(deptCache);
        return list;
    }
    class CacheBean {
        private String key;
        private long ttl;
        private long maximumSize;
        public String getKey() {
            return key;
        }
        public void setKey(String key) {
            this.key = key;
        }
        public long getTtl() {
            return ttl;
        }
        public void setTtl(long ttl) {
            this.ttl = ttl;
        }
        public long getMaximumSize() {
            return maximumSize;
        }
        public void setMaximumSize(long maximumSize) {
            this.maximumSize = maximumSize;
        }
    }
}

创建了一个SimpleCacheManager作为Cache的管理对象,然后初始化了两个Cache对象,分别存储user,dept类型的缓存。当然构建Cache的参数设置我写的比较简单,你在使用的时候酌情根据需要配置参数。

4. 使用注解来对 cache 增删改查

我们可以使用spring提供的 @Cacheable@CachePut@CacheEvict等注解来方便的使用caffeine缓存。

如果使用了多个cahce,比如redis、caffeine等,必须指定某一个CacheManage为@primary,在@Cacheable注解中没指定 cacheManager 则使用标记为primary的那个。

cache方面的注解主要有以下5个:

  • @Cacheable 触发缓存入口(这里一般放在创建和获取的方法上,@Cacheable注解会先查询是否已经有缓存,有会使用缓存,没有则会执行方法并缓存)
  • @CacheEvict 触发缓存的eviction(用于删除的方法上)
  • @CachePut 更新缓存且不影响方法执行(用于修改的方法上,该注解下的方法始终会被执行)
  • @Caching 将多个缓存组合在一个方法上(该注解可以允许一个方法同时设置多个注解)
  • @CacheConfig 在类级别设置一些缓存相关的共同配置(与其它缓存配合使用)

说一下@Cacheable@CachePut的区别:

@Cacheable:它的注解的方法是否被执行取决于Cacheable中的条件,方法很多时候都可能不被执行。

@CachePut:这个注解不会影响方法的执行,也就是说无论它配置的条件是什么,方法都会被执行,更多的时候是被用到修改上。

简要说一下Cacheable类中各个方法的使用:

public @interface Cacheable {
    /**
     * 要使用的cache的名字
     */
    @AliasFor("cacheNames")
    String[] value() default {};
    /**
     * 同value(),决定要使用那个/些缓存
     */
    @AliasFor("value")
    String[] cacheNames() default {};
    /**
     * 使用SpEL表达式来设定缓存的key,如果不设置默认方法上所有参数都会作为key的一部分
     */
    String key() default "";
    /**
     * 用来生成key,与key()不可以共用
     */
    String keyGenerator() default "";
    /**
     * 设定要使用的cacheManager,必须先设置好cacheManager的bean,这是使用该bean的名字
     */
    String cacheManager() default "";
    /**
     * 使用cacheResolver来设定使用的缓存,用法同cacheManager,但是与cacheManager不可以同时使用
     */
    String cacheResolver() default "";
    /**
     * 使用SpEL表达式设定出发缓存的条件,在方法执行前生效
     */
    String condition() default "";
    /**
     * 使用SpEL设置出发缓存的条件,这里是方法执行完生效,所以条件中可以有方法执行后的value
     */
    String unless() default "";
    /**
     * 用于同步的,在缓存失效(过期不存在等各种原因)的时候,如果多个线程同时访问被标注的方法
     * 则只允许一个线程通过去执行方法
     */
    boolean sync() default false;
}

基于注解的使用方法:

package com.rickiyang.learn.cache;
import com.rickiyang.learn.entity.User;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
/**
 * @author: rickiyang
 * @date: 2019/6/15
 * @description: 本地cache
 */
@Service
public class UserCacheService {
    /**
     * 查找
     * 先查缓存,如果查不到,会查数据库并存入缓存
     * @param id
     */
    @Cacheable(value = "userCache", key = "#id", sync = true)
    public void getUser(long id){
        //查找数据库
    }
    /**
     * 更新/保存
     * @param user
     */
    @CachePut(value = "userCache", key = "#user.id")
    public void saveUser(User user){
        //todo 保存数据库
    }
    /**
     * 删除
     * @param user
     */
    @CacheEvict(value = "userCache",key = "#user.id")
    public void delUser(User user){
        //todo 保存数据库
    }
}

如果你不想使用注解的方式去操作缓存,也可以直接使用SimpleCacheManager获取缓存的key进而进行操作。

注意到上面的key使用了spEL 表达式。Spring Cache提供了一些供我们使用的SpEL上下文数据,下表直接摘自Spring官方文档:

名称 位置 描述 示例
methodName root对象 当前被调用的方法名 #root.methodname
method root对象 当前被调用的方法 #root.method.name
target root对象 当前被调用的目标对象实例 #root.target
targetClass root对象 当前被调用的目标对象的类 #root.targetClass
args root对象 当前被调用的方法的参数列表 #root.args[0]
caches root对象 当前方法调用使用的缓存列表 #root.caches[0].name
Argument Name 执行上下文 当前被调用的方法的参数,如findArtisan(Artisan artisan),可以通过#artsian.id获得参数 #artsian.id
result 执行上下文 方法执行后的返回值(仅当方法执行后的判断有效,如 unless cacheEvict的beforeInvocation=false) #result

注意:

1.当我们要使用root对象的属性作为key时我们也可以将“#root”省略,因为Spring默认使用的就是root对象的属性。如

@Cacheable(key = "targetClass + methodName +#p0")

2.使用方法参数时我们可以直接使用“#参数名”或者“#p参数index”。如:

@Cacheable(value="userCache", key="#id")
@Cacheable(value="userCache", key="#p0")

SpEL提供了多种运算符

类型 运算符
关系 <,>,<=,>=,==,!=,lt,gt,le,ge,eq,ne
算术 +,- ,* ,/,%,^
逻辑 &&,||,!,and,or,not,between,instanceof
条件 ?: (ternary),?: (elvis)
正则表达式 matches
其他类型 ?.,?[…],![…],^[…],$[…]
相关文章
|
存储 缓存 算法
Google Guava之RateLimiter
在日常开发中,限流是高并发系统的三把守护利器之一,它的另外两个好兄弟缓存、降级下次再说。而限流在绝大多数场景中用来限制并发和请求量,像秒杀之类的高流量业务的场景,都能见到它的身影,所以它就是保护系统和下游的业务系统不被流量冲垮的利器。
240 6
Google Guava之RateLimiter
|
2月前
|
缓存 Java Maven
Google guava工具类库的介绍和使用
Google guava工具类库的介绍和使用
|
2月前
|
缓存 安全 Java
Google guava工具类的介绍和使用
Google guava工具类的介绍和使用
40 1
|
4月前
|
存储 缓存 NoSQL
Guava 缓存详解及使用
Guava Cache 是`Google Fuava`中的一个内存缓存模块,用于将数据缓存到JVM内存中。 本文主要介绍下Guava缓存的配置详解及相关使用 缓存分为本地缓存与分布式缓存。本地缓存为了保证线程安全问题,一般使用`ConcurrentMap`的方式保存在内存之中,而常见的分布式缓存则有`Redis`,`MongoDB`等。
|
5月前
|
缓存 NoSQL Java
SpringBoot:第五篇 集成Guava(本地缓存+分布式缓存)
SpringBoot:第五篇 集成Guava(本地缓存+分布式缓存)
76 0
|
6月前
|
存储 缓存 Java
Google Guava EventBus使用
Google Guava EventBus使用
40 0
|
7月前
|
设计模式 消息中间件 缓存
JAVA设计模式第五讲:设计模式在 Google Guava 的应用
JAVA设计模式第五讲:设计模式在 Google Guava 的应用
|
缓存 安全 JavaScript
别再造轮子了,Google 开源的 Guava 工具库真心强大!
别再造轮子了,Google 开源的 Guava 工具库真心强大!
|
Java API Maven
Guava:google公司开发的一款Java类库扩展工具包
Guava:google公司开发的一款Java类库扩展工具包
337 0
|
存储 缓存 索引
图解 Google V8 # 16:V8是怎么通过内联缓存来提升函数执行效率的?
图解 Google V8 # 16:V8是怎么通过内联缓存来提升函数执行效率的?
128 0
图解 Google V8 # 16:V8是怎么通过内联缓存来提升函数执行效率的?