springboot Caffeine 详解(一篇就明白)

1、添加依赖

首先考虑添加 maven 依赖。

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

            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context-support</artifactId>
                <version>5.1.10.RELEASE</version>
            </dependency>

2、springboot 配置 Caffeine 缓存

Caffeine 的教程可以参考 https://blog.csdn.net/dgh112233/article/details/118915259.

2.1、配置

先给个简单的例子:

@Configuration
@EnableCaching
public class CacheConfiguration {
    @Bean(name = "oneHourCacheManager")
    public CacheManager oneHourCacheManager(){
        Caffeine caffeine = Caffeine.newBuilder()
                .initialCapacity(10) //初始大小
                .maximumSize(11)  //最大大小
                .expireAfterWrite(1, TimeUnit.HOURS); //写入/更新之后1小时过期

        CaffeineCacheManager caffeineCacheManager = new CaffeineCacheManager();
        caffeineCacheManager.setAllowNullValues(true);
        caffeineCacheManager.setCaffeine(caffeine);
        return caffeineCacheManager;
    }
}

在 springboot 中使用 CaffeineCacheManager 管理器管理 Caffeine 类型的缓存,Caffeine 类似 Cache 缓存的工厂, 可以生产很多个 Cache 实例,Caffeine 可以设置各种缓存属性,这些 Cache 实例都共享 Caffeine 的缓存属性。
@EnableCaching 注解用于开启 springboot 的缓存功能,可以放在此处,也可以放在 application 启动类的头上。

2.2、使用缓存

简单给个例子:

@Cacheable(cacheManager = "oneHourCacheManager", value = "human", key = "#{name}")
public Person getPersonByName(String name){
	// 可以自定义代码,比如拿着 name 去数据库中查询此人的信息
}

@Cacheable 可以注解在某个类上,也可以注解在某个方法上,分别表示该类的所有方法都要使用缓存,该方法使用缓存。
比如上述代码,表示使用一个名字为 “human” 的缓存,如果缓存不存在,springboot 就会自动创建一个缓存,此缓存是由 Bean 名字为 oneHourCacheManager 的管理器所管理,当 Person getPersonByName(String name) 方法被调用时,参数 name 作为 key,先去缓存中查询 name 这个 key是否存在,如果存在,则直接根据 key 获取到 Person 实例对象,作为 getPersonByName(String name) 方法的返回值,如果不存在,则执行 getPersonByName(String name) 方法,从数据库中去查询,将查询到的 Person 实例对象存储进缓存中,再将 Person 对象实例作为方法的返回值。

3、CaffeineCacheManager 缓存管理器

3.1、源码简单说明

CaffeineCacheManager 管理着若干个缓存,至于怎么管理呢?先看一下源码,再慢慢说明。

public class CaffeineCacheManager implements CacheManager {
    private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap(16);
    private boolean dynamic = true;
    private Caffeine<Object, Object> cacheBuilder = Caffeine.newBuilder();
    @Nullable
    private CacheLoader<Object, Object> cacheLoader;
    private boolean allowNullValues = true;

    public CaffeineCacheManager() {
    }

    public CaffeineCacheManager(String... cacheNames) {
        this.setCacheNames(Arrays.asList(cacheNames));
    }

    public void setCacheNames(@Nullable Collection<String> cacheNames) {
        if (cacheNames != null) {
            Iterator var2 = cacheNames.iterator();
            while(var2.hasNext()) {
                String name = (String)var2.next();
                this.cacheMap.put(name, this.createCaffeineCache(name));
            }
            this.dynamic = false;
        } else {
            this.dynamic = true;
        }
    }

    public void setCaffeine(Caffeine<Object, Object> caffeine) {
        Assert.notNull(caffeine, "Caffeine must not be null");
        this.doSetCaffeine(caffeine);
    }

    public void setCaffeineSpec(CaffeineSpec caffeineSpec) {
        this.doSetCaffeine(Caffeine.from(caffeineSpec));
    }

    public void setCacheSpecification(String cacheSpecification) {
        this.doSetCaffeine(Caffeine.from(cacheSpecification));
    }

    public void setCacheLoader(CacheLoader<Object, Object> cacheLoader) {
        if (!ObjectUtils.nullSafeEquals(this.cacheLoader, cacheLoader)) {
            this.cacheLoader = cacheLoader;
            this.refreshKnownCaches();
        }
    }

    public void setAllowNullValues(boolean allowNullValues) {
        if (this.allowNullValues != allowNullValues) {
            this.allowNullValues = allowNullValues;
            this.refreshKnownCaches();
        }
    }

    public boolean isAllowNullValues() {
        return this.allowNullValues;
    }

    public Collection<String> getCacheNames() {
        return Collections.unmodifiableSet(this.cacheMap.keySet());
    }

    @Nullable
    public Cache getCache(String name) {
        Cache cache = (Cache)this.cacheMap.get(name);
        if (cache == null && this.dynamic) {
            ConcurrentMap var3 = this.cacheMap;
            synchronized(this.cacheMap) {
                cache = (Cache)this.cacheMap.get(name);
                if (cache == null) {
                    cache = this.createCaffeineCache(name);
                    this.cacheMap.put(name, cache);
                }
            }
        }
        return cache;
    }

    protected Cache createCaffeineCache(String name) {
        return new CaffeineCache(name, this.createNativeCaffeineCache(name), this.isAllowNullValues());
    }

    protected com.github.benmanes.caffeine.cache.Cache<Object, Object> createNativeCaffeineCache(String name) {
        return (com.github.benmanes.caffeine.cache.Cache)(this.cacheLoader != null ? this.cacheBuilder.build(this.cacheLoader) : this.cacheBuilder.build());
    }

    private void doSetCaffeine(Caffeine<Object, Object> cacheBuilder) {
        if (!ObjectUtils.nullSafeEquals(this.cacheBuilder, cacheBuilder)) {
            this.cacheBuilder = cacheBuilder;
            this.refreshKnownCaches();
        }
    }

    private void refreshKnownCaches() {
        Iterator var1 = this.cacheMap.entrySet().iterator();
        while(var1.hasNext()) {
            Entry<String, Cache> entry = (Entry)var1.next();
            entry.setValue(this.createCaffeineCache((String)entry.getKey()));
        }
    }
}

首先,用 ConcurrentMap cacheMap 来保存各个缓存 Cache,key 是缓存的名字,value 就是对应的缓存对象。
boolean dynamic = true; 这个变量的意义是,当根据名字来获取某个缓存时,如果缓存不存在,那么是否自动创建一个缓存。
Caffeine cacheBuilder = Caffeine.newBuilder():自带了一个 Caffeine,Caffeine 的各种属性都使用默认的属性,因为 Caffeine.newBuilder() 没有给 Caffeine 设置任何属性。
CacheLoader cacheLoader :这是一个函数式参数,可以用lambda表示,也可以用函数表示,它的作用是什么呢?等一下讲。
boolean allowNullValues:是否允许存入 null 值。

3.2、基本原理

Caffeine 关联的缓存有三种:Cache,LoadingCache 和 AsyncLoadingCache。
而 springboot + CaffeineCacheManager 所使用的是 LoadingCache。
简单看一下 LoadingCache 是怎么创建的:

        LoadingCache<String, Person> loadingCache = Caffeine.newBuilder()
                .maximumSize(3)
                .build(k->new Person(14, "ZhangSan"));

        LoadingCache<String, Person> loadingCache1 = Caffeine.newBuilder()
                .maximumSize(3)
                .build((String key)->{
                    return new Person(14, "ZhangSan");
                });

build 方法的参数类型就是 CacheLoader,是一个函数式参数,上述代码中第一种就是 lambda 表达式,第二种就是函数式。CacheLoader 这个函数的规定是,将 key 作为参数,返回值作为 value。
所以说,springboot 在创建 Caffeine 缓存时,往往需要指定 CacheLoader 参数,但是这个 CacheLoader 参数是什么呢?从哪里来呢? 看看前面的例子代码,@Cacheable 不就注解在方法上的吗,方法不就是函数吗,所以 springboot 会将 @Cacheable 注解的方法挨个地作为 CacheLoader 参数,然后创建相应的缓存,这样,缓存和方法就挂钩了,每次调用方法时,先查询缓存,如果缓存有,直接取缓存中的数据,如果缓存没有,则执行方法,将方法的返回值作为 value 存入缓存。

你可能感兴趣的:(Java,spring,Caffeine,springboot,Cacheable,CacheManager,原理)