天天看點

深入剖析Spring緩存的内部實作機制:緩存實作原理

作者:程式員進階碼農II

緩存實作原理

承接前面的内容,本節将深入剖析Spring緩存的内部實作機制。

對于緩存而言,其核心思想是在調用一個緩存方法時把該方法的參數和傳回結果作為一個鍵值對存放在緩存中,等到下次基于同樣的參數來調用該方法時将不再執行該方法,而是直接從緩存中擷取結果并進行傳回。

是以在使用緩存的時候我們要確定緩存的方法對于相同的參數要有相同的傳回結果。想要實作這一效果有很多種政策,Spring是怎麼做的呢?

在對緩存機制的整體設計上,Spring采用了典型的兩層架構,即核心層和擴充層。所謂核心層,相當于對緩存本身的一種抽象,抽取了與緩存相關的最核心的操作方法;而擴充層,則是基于核心層的抽象,分别內建業界主流的緩存工具,進而對緩存的核心操作方法提供實作方案。Spring緩存中的核心層和擴充層的關系示意圖如圖6-2所示。

圖6-2是我們了解Spring緩存實作原理的一大切入點,也是Spring緩存的架構設計思想的展現。

深入剖析Spring緩存的内部實作機制:緩存實作原理

圖6-2 Spring緩存核心層和擴充層的關系示意圖

Cache和CacheManager接口

在Spring緩存中,我們可以簡單地把Cache和CacheManager接口定義為核心層元件,而把兩個接口的各種實作類看作擴充元件。我們先來看Cache接口,定義如代碼清單6-26所示。

代碼清單6-26 Cache接口定義代碼

public interface Cache {

//定義緩存的名稱

String getName();

//擷取緩存的底層實作,例如EhCache等

Object getNativeCache();

//根據key得到一個ValueWrapper,可以通過ValueWrapper擷取緩存值

ValueWrapper get(Object key);

//根據key和value的類型直接擷取緩存值

<T> T get(Object key, Class<T> type);

//根據key擷取緩存值,擷取過程交給了一個回調函數

<T> T get(Object key, Callable<T> valueLoader);

//根據key向緩存中放資料

void put(Object key, Object value);

//根據key向緩存中放資料,在緩存值尚未就緒的情況下傳回一個ValueWrapper

ValueWrapper putIfAbsent(Object key, Object value);

//從緩存中移除key對應的緩存

void evict(Object key);

//清空緩存

void clear();

//緩存值的包裝器接口

interface ValueWrapper {

//擷取真實的緩存值

Object get();

}

}

Cache接口的設計展現了Spring對緩存操作本身的高度抽象,除了可以預見到的get、put、evict以及clear等操作之外,我們還看到了用于對緩存值進行包裝的ValueWrapper接口,顯然這是包裝器模式的一種典型應用。

在Spring中,存在一大批Cache接口的實作類,它們之間的類層關系如圖6-3所示。

深入剖析Spring緩存的内部實作機制:緩存實作原理

圖6-3 Cache接口的類層關系圖

可以看到Cache接口存在一個抽象實作類AbstractValueAdaptingCache,而基于該抽象類,Spring內建了業界主流的一批緩存工具。其中,ConcurrentMapCache使用java.util.concurrent.ConcurrentHashMap實作了Cache接口,GuavaCache對com.google.common.cache.Cache進行了包裝,CaffeineCache則是內建了從Java 8引入的高性能的緩存庫Caffeine等。

另外,我們熟悉的EhCacheCache則直接實作了Cache接口。

以ConcurrentMapCache為例,我們來看它的幾個核心方法的實作過程。

顯然它的getNativeCache()傳回的應該是一個ConcurrentMap對象,而它的putIfAbsent()方法如代碼清單6-27所示。

代碼清單6-27 putIfAbsent()方法代碼

@Override

public ValueWrapper putIfAbsent(Object key, Object value) {

Object existing = this.store.putIfAbsent(key,

toStoreValue(value)); return toValueWrapper(existing);

}

這裡先儲存緩存值,然後調用toValueWrapper()方法傳回了一個包裝器類ValueWrapper,toValueWrapper()方法的實作如代碼清單6-28所示。

代碼清單6-28 toValueWrapper ()方法代碼

protected Cache.ValueWrapper toValueWrapper(Object storeValue) {

return (storeValue != null ? new

SimpleValueWrapper(fromStoreValue(storeValue)) : null);

}

可以看到,這時候會建構一個SimpleValueWrapper來擷取真正的緩存值,這是Value-Wrapper最基本的實作,内部隻是簡單持有目前的緩存值而已。然後,我們再來看如代碼清單6-29所示的get()方法。

代碼清單6-29 get()方法代碼

@Override

public <T> T get(Object key, Callable<T> valueLoader) {

if (this.store.containsKey(key)) {

return (T) get(key).get();

}

else {

synchronized (this.store) {

//如果緩存值存在,則直接傳回

if (this.store.containsKey(key)) {

return (T) get(key).get();

}

T value;

try {

//否則建立目标值 value = valueLoader.call();

}

catch (Throwable ex) {

throw new ValueRetrievalException(key, valueLoader,

ex);

}

//把目标值緩存起來再傳回

put(key, value);

return value;

}

}

}

這個get()方法的實作比較複雜,其中的valueLoader相當于一個異步回調,用于處理沒有命中緩存的情況,進而滿足通用的if cached, return;

otherwise create, cache and return這一緩存處理模式,即如果緩存值存在則直接傳回;否則建立目标值,把它緩存起來再傳回。同時,在并發通路下,我們需要確定加載緩存值的操作僅發生一次,是以這裡使用了synchronized關鍵字來實作同步操作。

介紹完Cache接口之後,我們再來看CacheManager接口。Spring設計并實作Cache-Manager的目的在于:應用程式中并不是僅僅使用一個緩存,而是可以使用多個,是以需要CacheManager接口來對這些緩存進行統一管理,這也是一種抽象。

CacheManager接口定義如代碼清單6-30所示。

代碼清單6-30 CacheManager接口定義代碼

public interface CacheManager {

//根據Cache名擷取Cache

Cache getCache(String name);

//擷取所有Cache的名稱 Collection<String> getCacheNames();

}

同樣,CacheManager也存在一組實作類,在前面的内容中,我們已經看到過它的類層關系圖。在它的抽象實作類AbstractCacheManager以及具體實作類ConcurrentMapCache-Manager和CaffeineCacheManager中,可以看到Spring使用一個ConcurrentHashMap來儲存所有的Cache對象,如代碼清單6-31所示。

代碼清單6-31 cacheMap變量定義代碼

private final ConcurrentMap<String, Cache> cacheMap = new

ConcurrentHashMap<String, Cache>(16);

而對不同的緩存工具,則會建立不同的Cache實作類。以CaffeineCacheManager為例,它會使用如代碼清單6-32所示的createCaffeineCache()方法來建立一個CaffeineCache對象。

代碼清單6-32 createCaffeineCache()方法代碼

protected Cache createCaffeineCache(String name) {

return new CaffeineCache(name, createNativeCaffeineCache(name),

isAllowNullValues());

}

在本章後續内容中,我們還将基于Cache和CacheManager抽象元件,來給出擴充它們的方法和案例。

CacheInterceptor攔截器

現在,我們已經了解了Spring緩存的整體架構,接下來就對基于注解的緩存處理流程展開讨論,看看Spring是如何在方法執行過程中嵌入緩存處理機制的。我們先從@EnableCaching注解切入,該注解定義如代碼清單6-33所示。

代碼清單6-33 @EnableCaching注解代碼

@Target(ElementType.TYPE)

@Retention(RetentionPolicy.RUNTIME)

@Documented

@Import(CachingConfigurationSelector.class)

public @interface EnableCaching {

//是否啟用CGLIB代理,預設為false,即使用JDK代理

boolean proxyTargetClass() default false;

//切面通知模式,預設使用代理模式

AdviceMode mode() default AdviceMode.PROXY;

//BeanPostProcessor執行的順序

int order() default Ordered.LOWEST_PRECEDENCE;

}

@EnableCaching注解中的三個屬性非常具有代表性。Spring緩存本質上都是通過AOP來實作的,是以在這個注解中,我們看到了與代理和切面相關的屬性。關于AOP,我們在第3章中有深入的分析,你可以進行回顧。

另外,與代理機制相關的實作也往往與Spring的後置處理器BeanPostProcessor有關。

在Spring中,很多以@EnableXXX命名的注解都包含這幾個配置項,例如在第10章中将要介紹到的@EnableAsync注解。

在@EnableCaching注解中,我們發現導入了CachingConfigurationSelector這個類。然後,我們順着代碼的執行流程來到了ProxyCachingConfiguration,發現在這裡建立了一個CacheInterceptor攔截器,如代碼清單6-34所示。

代碼清單6-34 cacheInterceptor()方法代碼

public CacheInterceptor cacheInterceptor() {

CacheInterceptor interceptor = new CacheInterceptor();

interceptor.setCacheOperationSources(cacheOperationSource());

if (this.cacheResolver != null) {

interceptor.setCacheResolver(this.cacheResolver);

}

else if (this.cacheManager != null) {

interceptor.setCacheManager(this.cacheManager);

}

if (this.keyGenerator != null) {

interceptor.setKeyGenerator(this.keyGenerator);

}

if (this.errorHandler != null) {

interceptor.setErrorHandler(this.errorHandler);

}

return interceptor;

}

顯然,這是一個典型的攔截器類,設定了CacheManager、KeyGenerator等核心對象,它的職責是在目标方法上執行具體的緩存操作。

CacheInterceptor的代碼結構如代碼清單6-35所示。

代碼清單6-35 CacheInterceptor類代碼

public class CacheInterceptor extends CacheAspectSupport implements

MethodInterceptor, Serializable {

@Override

public Object invoke(final MethodInvocation invocation) throws

Throwable {

Method method = invocation.getMethod()

CacheOperationInvoker aopAllianceInvoker = new

CacheOperationInvoker() {

...

}; try {

//執行攔截邏輯

return execute(aopAllianceInvoker, invocation.getThis(),

method, invocation.getArguments());

}

...

}

}

我們來進一步分析這裡的execute()方法,其内部包含的緩存邏輯主要展現在如代碼清單6-36所示的這段代碼中。

代碼清單6-36 execute()方法代碼

//判斷緩存條件

if (isConditionPassing(context,

CacheOperationExpressionEvaluator.NO_RESULT)) {

//擷取緩存鍵

Object key = generateKey(context,

CacheOperationExpressionEvaluator.NO_RESULT);

//擷取Cache對象

Cache cache = context.getCaches().iterator().next();

try {

// 調用Cache.get(key, Callable)方法擷取緩存值

return wrapCacheValue(method, cache.get(key, new

Callable<Object>() {

@Override

public Object call() throws Exception {

return unwrapReturnValue(invokeOperation(invoker));

}

}));

}

...

}

最終,代碼的流程執行到了我們熟悉的cache.get(key, Callable)方法。這樣,通過一個攔截器元件CacheInterceptor,我們就把目标方法的執行過程和緩存機制整合在了一起。

本文給大家講解的内容是springboot内置緩存:打造高性能系統緩存,緩存實作原理

  • 下文給大家講解的是springboot内置緩存:打造高性能系統緩存,系統緩存實戰經驗