天天看點

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<String, Cache> cacheMap 來儲存各個緩存 Cache,key 是緩存的名字,value 就是對應的緩存對象。

boolean dynamic = true; 這個變量的意義是,當根據名字來擷取某個緩存時,如果緩存不存在,那麼是否自動建立一個緩存。

Caffeine<Object, Object> cacheBuilder = Caffeine.newBuilder():自帶了一個 Caffeine,Caffeine 的各種屬性都使用預設的屬性,因為 Caffeine.newBuilder() 沒有給 Caffeine 設定任何屬性。

CacheLoader<Object, Object> 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 存入緩存。