前言
上篇文章介紹了@EnableCaching
瓣距,用它來開啟Spring對緩存注解的支持。本篇文章將繼續(xù)分析Spring Cache
,并且講解的是我們最為關(guān)心的:緩存注解實(shí)操方面的原理支持和使用。
開發(fā)過程中因注解的優(yōu)雅、使用簡單使得這種方式廣泛被大家所接受和使用猴誊,本文將按照先原理,再實(shí)操的步驟侮措,一步步解惑Spring緩存注解的原理
緩存注解
關(guān)于Spring的緩存注解懈叹,一共有如下5個(gè):
-
@Cacheable
:緩存
// @since 3.1 可以標(biāo)注在方法上、類上 下同
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Cacheable {
// 緩存名稱 可以寫多個(gè)~
@AliasFor("cacheNames")
String[] value() default {};
@AliasFor("value")
String[] cacheNames() default {};
// 支持寫SpEL分扎,切可以使用#root
String key() default "";
// Mutually exclusive:它和key屬性互相排斥澄成。請只使用一個(gè)
String keyGenerator() default "";
String cacheManager() default "";
String cacheResolver() default "";
// SpEL,可以使用#root畏吓。 只有true時(shí)墨状,才會作用在這個(gè)方法上
String condition() default "";
// 可以寫SpEL #root,并且可以使用#result拿到方法返回值~~~
String unless() default "";
// true:表示強(qiáng)制同步執(zhí)行菲饼。(若多個(gè)線程試圖為**同一個(gè)鍵**加載值肾砂,以同步的方式來進(jìn)行目標(biāo)方法的調(diào)用)
// 同步的好處是:后一個(gè)線程會讀取到前一個(gè)緩存的緩存數(shù)據(jù),不用再查庫了~~~
// 默認(rèn)是false宏悦,不開啟同步one by one的
// @since 4.3 注意是sync而不是Async
// 它的解析依賴于Spring4.3提供的Cache.get(Object key, Callable<T> valueLoader);方法
boolean sync() default false;
}
-
@CachePut
:緩存更新
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Cacheable {
@AliasFor("cacheNames")
String[] value() default {};
@AliasFor("value")
String[] cacheNames() default {};
// 注意:它和上面區(qū)別是镐确。此處key它還能使用#result
String key() default "";
String keyGenerator() default "";
String cacheManager() default "";
String cacheResolver() default "";
String condition() default "";
String unless() default "";
}
-
@CacheEvict
:緩存刪除
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Cacheable {
@AliasFor("cacheNames")
String[] value() default {};
@AliasFor("value")
String[] cacheNames() default {};
// 它也能使用#result
String key() default "";
String keyGenerator() default "";
String cacheManager() default "";
String cacheResolver() default "";
String condition() default "";
// 是否把上面cacheNames指定的所有的緩存都清除掉,默認(rèn)false
boolean allEntries() default false;
// 是否讓清理緩存動作在目標(biāo)方法之前執(zhí)行饼煞,默認(rèn)是false(在目標(biāo)方法之后執(zhí)行)
// 注意:若在之后執(zhí)行的話源葫,目標(biāo)方法一旦拋出異常了,那緩存就清理不掉了~~~~
boolean beforeInvocation() default false;
}
-
@Caching
:用于處理復(fù)雜的緩存情況砖瞧。比如用戶既要根據(jù)id緩存一份息堂,也要根據(jù)電話緩存一份,還要根據(jù)電子郵箱緩存一份,就可以使用它
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Caching {
Cacheable[] cacheable() default {};
CachePut[] put() default {};
CacheEvict[] evict() default {};
}
-
@CacheConfig
:可以在類級別上標(biāo)注一些共用的緩存屬性荣堰。(所有方法共享床未,@since 4.1)
// @since 4.1 出現(xiàn)得還是比較晚的
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CacheConfig {
String[] cacheNames() default {};
String keyGenerator() default "";
String cacheManager() default "";
String cacheResolver() default "";
}
原理分析
先閱讀:【小家Spring】玩轉(zhuǎn)Spring Cache — @Cacheable/@CachePut/@CacheEvict緩存注解相關(guān)基礎(chǔ)類打點(diǎn) 再讀本文,效果會像德芙一般絲滑~
從上篇文章中已經(jīng)知道了@EnableCaching
主要向容器注入了三個(gè)Bean:CacheOperationSource
振坚、BeanFactoryCacheOperationSourceAdvisor
即硼、CacheInterceptor
。他們是讓注解生效
的核心類屡拨。
CacheOperationSource
它代表緩存操作源,已經(jīng)分析過褥实。
BeanFactoryCacheOperationSourceAdvisor
從名字就能看出它是一個(gè)增強(qiáng)器Advisor
呀狼,并且還和BeanFactory
有關(guān)。
@Bean(name = CacheManagementConfigUtils.CACHE_ADVISOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public BeanFactoryCacheOperationSourceAdvisor cacheAdvisor() {
BeanFactoryCacheOperationSourceAdvisor advisor = new BeanFactoryCacheOperationSourceAdvisor();
advisor.setCacheOperationSource(cacheOperationSource());
advisor.setAdvice(cacheInterceptor());
if (this.enableCaching != null) {
advisor.setOrder(this.enableCaching.<Integer>getNumber("order"));
}
return advisor;
}
從上配置知道损离,這個(gè)增強(qiáng)器的切面Advice是CacheInterceptor
哥艇,并且持有CacheOperationSource
的引用。
public class BeanFactoryCacheOperationSourceAdvisor extends AbstractBeanFactoryPointcutAdvisor {
@Nullable
private CacheOperationSource cacheOperationSource;
// 切面Pointcut
private final CacheOperationSourcePointcut pointcut = new CacheOperationSourcePointcut() {
@Override
@Nullable
protected CacheOperationSource getCacheOperationSource() {
return cacheOperationSource;
}
};
public void setCacheOperationSource(CacheOperationSource cacheOperationSource) {
this.cacheOperationSource = cacheOperationSource;
}
// 注意:此處你可以自定義一個(gè)ClassFilter僻澎,過濾掉你想忽略的類
public void setClassFilter(ClassFilter classFilter) {
this.pointcut.setClassFilter(classFilter);
}
@Override
public Pointcut getPointcut() {
return this.pointcut;
}
}
此Advisor
的實(shí)現(xiàn)非常的簡單貌踏,切點(diǎn)是CacheOperationSourcePointcut
,核心邏輯都依托于緩存屬性源窟勃。所以還沒有看這塊的祖乳,此處再一次推薦:【小家Spring】玩轉(zhuǎn)Spring Cache — @Cacheable/@CachePut/@CacheEvict緩存注解相關(guān)基礎(chǔ)類打點(diǎn)
CacheInterceptor
緩存攔截器。先說明一點(diǎn)秉氧,它的實(shí)現(xiàn)模式幾乎和TransactionInterceptor
一毛一樣眷昆。所以我又想建議一句了,有空先看看它吧:【小家Spring】源碼分析Spring的事務(wù)攔截器:TransactionInterceptor和事務(wù)管理器:PlatformTransactionManager
同樣汁咏,CacheInterceptor
是緩存真正執(zhí)行的核心亚斋,處理邏輯還是稍顯復(fù)雜的。
// @since 3.1 它是個(gè)MethodInterceptor環(huán)繞增強(qiáng)器~~~
public class CacheInterceptor extends CacheAspectSupport implements MethodInterceptor, Serializable {
@Override
@Nullable
public Object invoke(final MethodInvocation invocation) throws Throwable {
Method method = invocation.getMethod();
// 采用函數(shù)的形式攘滩,最終把此函數(shù)傳交給父類的execute()去執(zhí)行
// 但是很顯然帅刊,最終**執(zhí)行目標(biāo)方法**的是invocation.proceed();它
//這里就是對執(zhí)行方法調(diào)用的一次封裝,主要是為了處理對異常的包裝漂问。
CacheOperationInvoker aopAllianceInvoker = () -> {
try {
return invocation.proceed();
}
catch (Throwable ex) {
throw new CacheOperationInvoker.ThrowableWrapper(ex);
}
};
try {
// //真正地去處理緩存操作的執(zhí)行赖瞒,很顯然這是父類的方法,所以我們要到父類CacheAspectSupport中去看看级解。
return execute(aopAllianceInvoker, invocation.getThis(), method, invocation.getArguments());
} catch (CacheOperationInvoker.ThrowableWrapper th) {
throw th.getOriginal();
}
}
}
這個(gè)類本身的實(shí)現(xiàn)很少冒黑,主要邏輯都在他的抽象父類:CacheAspectSupport
CacheAspectSupport
它類似于TransactionAspectSupport
,父類實(shí)現(xiàn)了所有的核心邏輯
// @since 3.1 它相較于TransactionAspectSupport額外實(shí)現(xiàn)了SmartInitializingSingleton接口
// SmartInitializingSingleton應(yīng)該也不會陌生勤哗。它在初始化完所有的單例Bean后會執(zhí)行這個(gè)接口的`afterSingletonsInstantiated()`方法
// 比如我們熟悉的ScheduledAnnotationBeanPostProcessor抡爹、EventListenerMethodProcessor都是這么來處理的
// 另外還需要注意,它還繼承自AbstractCacheInvoker:主要對異常情況用CacheErrorHandler處理
public abstract class CacheAspectSupport extends AbstractCacheInvoker implements BeanFactoryAware, InitializingBean, SmartInitializingSingleton {
// CacheOperationCacheKey:緩存的key CacheOperationMetadata就是持有一些基礎(chǔ)屬性的性息
// 這個(gè)緩存挺大芒划,相當(dāng)于每一個(gè)類冬竟、方法都有氣對應(yīng)的**緩存屬性元數(shù)據(jù)**
private final Map<CacheOperationCacheKey, CacheOperationMetadata> metadataCache = new ConcurrentHashMap<>(1024);
// 解析一些condition欧穴、key、unless等可以寫el表達(dá)式的處理器~~~
// 之前講過的熟悉的有:EventExpressionEvaluator
private final CacheOperationExpressionEvaluator evaluator = new CacheOperationExpressionEvaluator();
// 屬性源泵殴,默認(rèn)情況下是基于注解的`AnnotationCacheOperationSource`
@Nullable
private CacheOperationSource cacheOperationSource;
// 看到了吧 key生成器默認(rèn)使用的SimpleKeyGenerator
// 注意SingletonSupplier是Spring5.1的新類涮帘,實(shí)現(xiàn)了接口java.util.function.Supplier 主要是對null值進(jìn)行了容錯(cuò)
private SingletonSupplier<KeyGenerator> keyGenerator = SingletonSupplier.of(SimpleKeyGenerator::new);
@Nullable
private SingletonSupplier<CacheResolver> cacheResolver;
@Nullable
private BeanFactory beanFactory;
private boolean initialized = false;
// @since 5.1
public void configure(@Nullable Supplier<CacheErrorHandler> errorHandler, @Nullable Supplier<KeyGenerator> keyGenerator,@Nullable Supplier<CacheResolver> cacheResolver, @Nullable Supplier<CacheManager> cacheManager) {
// 第二個(gè)參數(shù)都是默認(rèn)值,若調(diào)用者沒傳的話
this.errorHandler = new SingletonSupplier<>(errorHandler, SimpleCacheErrorHandler::new);
this.keyGenerator = new SingletonSupplier<>(keyGenerator, SimpleKeyGenerator::new);
this.cacheResolver = new SingletonSupplier<>(cacheResolver, () -> SimpleCacheResolver.of(SupplierUtils.resolve(cacheManager)));
}
// 此處:若傳入了多個(gè)cacheOperationSources笑诅,那最終使用的就是CompositeCacheOperationSource包裝起來
// 所以發(fā)現(xiàn)调缨,Spring是支持我們多種 緩存屬性源的
public void setCacheOperationSources(CacheOperationSource... cacheOperationSources) {
Assert.notEmpty(cacheOperationSources, "At least 1 CacheOperationSource needs to be specified");
this.cacheOperationSource = (cacheOperationSources.length > 1 ? new CompositeCacheOperationSource(cacheOperationSources) : cacheOperationSources[0]);
}
// @since 5.1 單數(shù)形式的設(shè)置
public void setCacheOperationSource(@Nullable CacheOperationSource cacheOperationSource) {
this.cacheOperationSource = cacheOperationSource;
}
... // 省略各種get/set方法~~~
// CacheOperationSource必須不為null,因?yàn)橐磺幸劳杏谒? @Override
public void afterPropertiesSet() {
Assert.state(getCacheOperationSource() != null, "The 'cacheOperationSources' property is required: " + "If there are no cacheable methods, then don't use a cache aspect.");
}
// 這個(gè)來自于接口:SmartInitializingSingleton 在實(shí)例化完所有單例Bean后調(diào)用
@Override
public void afterSingletonsInstantiated() {
// 若沒有給這個(gè)切面手動設(shè)置cacheResolver 那就去拿CacheManager吧
// 這就是為何我們只需要把CacheManager配進(jìn)容器里即可 就自動會設(shè)置在切面里了
if (getCacheResolver() == null) {
// Lazily initialize cache resolver via default cache manager...
Assert.state(this.beanFactory != null, "CacheResolver or BeanFactory must be set on cache aspect");
try {
// 請注意:這個(gè)方法實(shí)際上是把CacheManager包裝成了一個(gè)SimpleCacheResolver
// 所以最終還是給SimpleCacheResolver賦值
setCacheManager(this.beanFactory.getBean(CacheManager.class));
} ...
}
this.initialized = true;
}
// 主要為了輸出日志吆你,子類可復(fù)寫
protected String methodIdentification(Method method, Class<?> targetClass) {
Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
return ClassUtils.getQualifiedMethodName(specificMethod);
}
// 從這里也能看出弦叶,至少要指定一個(gè)Cache才行(也就是cacheNames)
protected Collection<? extends Cache> getCaches(CacheOperationInvocationContext<CacheOperation> context, CacheResolver cacheResolver) {
Collection<? extends Cache> caches = cacheResolver.resolveCaches(context);
if (caches.isEmpty()) {
throw new IllegalStateException("No cache could be resolved for '" +
context.getOperation() + "' using resolver '" + cacheResolver +
"'. At least one cache should be provided per cache operation.");
}
return caches;
}
// 這個(gè)根據(jù)CacheOperation 這部分還是比較重要的
protected CacheOperationMetadata getCacheOperationMetadata(CacheOperation operation, Method method, Class<?> targetClass) {
CacheOperationCacheKey cacheKey = new CacheOperationCacheKey(operation, method, targetClass);
CacheOperationMetadata metadata = this.metadataCache.get(cacheKey);
if (metadata == null) {
// 1、指定了KeyGenerator就去拿這個(gè)Bean(沒有就報(bào)錯(cuò)妇多,所以key不要寫錯(cuò)了)
// 沒有指定就用默認(rèn)的
KeyGenerator operationKeyGenerator;
if (StringUtils.hasText(operation.getKeyGenerator())) {
operationKeyGenerator = getBean(operation.getKeyGenerator(), KeyGenerator.class);
} else {
operationKeyGenerator = getKeyGenerator();
}
// 1伤哺、自己指定的CacheResolver
// 2、再看指定的的CacheManager,包裝成一個(gè)SimpleCacheResolver
// 3者祖、
CacheResolver operationCacheResolver;
if (StringUtils.hasText(operation.getCacheResolver())) {
operationCacheResolver = getBean(operation.getCacheResolver(), CacheResolver.class);
} else if (StringUtils.hasText(operation.getCacheManager())) {
CacheManager cacheManager = getBean(operation.getCacheManager(), CacheManager.class);
operationCacheResolver = new SimpleCacheResolver(cacheManager);
} else { //最終都沒配置的話立莉,取本切面默認(rèn)的
operationCacheResolver = getCacheResolver();
Assert.state(operationCacheResolver != null, "No CacheResolver/CacheManager set");
}
// 封裝成Metadata
metadata = new CacheOperationMetadata(operation, method, targetClass, operationKeyGenerator, operationCacheResolver);
this.metadataCache.put(cacheKey, metadata);
}
return metadata;
}
// qualifiedBeanOfType的意思是,@Bean類上面標(biāo)注@Qualifier注解也生效
protected <T> T getBean(String beanName, Class<T> expectedType) {
if (this.beanFactory == null) {
throw new IllegalStateException(
"BeanFactory must be set on cache aspect for " + expectedType.getSimpleName() + " retrieval");
}
return BeanFactoryAnnotationUtils.qualifiedBeanOfType(this.beanFactory, expectedType, beanName);
}
// 請Meta數(shù)據(jù)的緩存
protected void clearMetadataCache() {
this.metadataCache.clear();
this.evaluator.clear();
}
// 父類最為核心的方法七问,真正執(zhí)行目標(biāo)方法 + 緩存操作
@Nullable
protected Object execute(CacheOperationInvoker invoker, Object target, Method method, Object[] args) {
// Check whether aspect is enabled (to cope with cases where the AJ is pulled in automatically)
// 如果已經(jīng)表示初始化過了(有CacheManager蜓耻,CacheResolver了),執(zhí)行這里
if (this.initialized) {
// getTargetClass拿到原始Class 解剖代理(N層都能解開)
Class<?> targetClass = getTargetClass(target);
CacheOperationSource cacheOperationSource = getCacheOperationSource();
if (cacheOperationSource != null) {
// 簡單的說就是拿到該方法上所有的CacheOperation緩存操作械巡,最終一個(gè)一個(gè)的執(zhí)行~~~~
Collection<CacheOperation> operations = cacheOperationSource.getCacheOperations(method, targetClass);
if (!CollectionUtils.isEmpty(operations)) {
// CacheOperationContexts是非常重要的一個(gè)私有內(nèi)部類
// 注意它是復(fù)數(shù)哦~不是CacheOperationContext單數(shù) 所以它就像持有多個(gè)注解上下文一樣 一個(gè)個(gè)執(zhí)行吧
// 所以我建議先看看此類的描述媒熊,再繼續(xù)往下看~~~
return execute(invoker, method, new CacheOperationContexts(operations, method, args, target, targetClass));
}
}
}
// 若還沒初始化 直接執(zhí)行目標(biāo)方法即可
return invoker.invoke();
}
@Nullable
private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {
// Special handling of synchronized invocation
// 如果是需要同步執(zhí)行的話,這塊還是
if (contexts.isSynchronized()) {
CacheOperationContext context = contexts.get(CacheableOperation.class).iterator().next();
if (isConditionPassing(context, CacheOperationExpressionEvaluator.NO_RESULT)) {
Object key = generateKey(context, CacheOperationExpressionEvaluator.NO_RESULT);
Cache cache = context.getCaches().iterator().next();
try {
return wrapCacheValue(method, cache.get(key, () -> unwrapReturnValue(invokeOperation(invoker))));
}
catch (Cache.ValueRetrievalException ex) {
// The invoker wraps any Throwable in a ThrowableWrapper instance so we
// can just make sure that one bubbles up the stack.
throw (CacheOperationInvoker.ThrowableWrapper) ex.getCause();
}
}
else {
// No caching required, only call the underlying method
return invokeOperation(invoker);
}
}
// sync=false的情況坟比,走這里~~~
// Process any early evictions beforeInvocation=true的會在此處最先執(zhí)行~~~
// 最先處理@CacheEvict注解~~~真正執(zhí)行的方法請參見:performCacheEvict
// context.getCaches()拿出所有的caches芦鳍,看看是執(zhí)行cache.evict(key);方法還是cache.clear();而已
// 需要注意的的是context.isConditionPassing(result); condition條件此處生效,并且可以使用#result
// context.generateKey(result)也能使用#result
// @CacheEvict沒有unless屬性
processCacheEvicts(contexts.get(CacheEvictOperation.class), true, CacheOperationExpressionEvaluator.NO_RESULT);
// 執(zhí)行@Cacheable 看看緩存是否能夠命中
Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class));
// Collect puts from any @Cacheable miss, if no cached item is found
List<CachePutRequest> cachePutRequests = new LinkedList<>();
// 如果緩存沒有命中葛账,那就準(zhǔn)備一個(gè)cachePutRequest
// 因?yàn)锧Cacheable首次進(jìn)來肯定命中不了柠衅,最終肯定是需要執(zhí)行一次put操作的~~~這樣下次進(jìn)來就能命中了呀
if (cacheHit == null) {
collectPutRequests(contexts.get(CacheableOperation.class), CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);
}
Object cacheValue;
Object returnValue;
// 如果緩存命中了,并且并且沒有@CachePut的話籍琳,也就直接返回了~~
if (cacheHit != null && !hasCachePut(contexts)) {
// If there are no put requests, just use the cache hit
cacheValue = cacheHit.get();
// wrapCacheValue主要是支持到了Optional
returnValue = wrapCacheValue(method, cacheValue);
} else { //到此處菲宴,目標(biāo)方法就肯定是需要執(zhí)行了的~~~~~
// Invoke the method if we don't have a cache hit
// 啥都不說,先invokeOperation執(zhí)行目標(biāo)方法趋急,拿到方法的的返回值 后續(xù)在處理put啥的
returnValue = invokeOperation(invoker);
cacheValue = unwrapReturnValue(returnValue);
}
// Collect any explicit @CachePuts explicit:明確的
collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);
// Process any collected put requests, either from @CachePut or a @Cacheable miss
for (CachePutRequest cachePutRequest : cachePutRequests) {
// 注意:此處unless啥的生效~~~~
// 最終執(zhí)行cache.put(key, result);方法
cachePutRequest.apply(cacheValue);
}
// Process any late evictions beforeInvocation=true的會在此處最先執(zhí)行~~~ beforeInvocation=false的會在此處最后執(zhí)行~~~
// 所以中途若拋出異常喝峦,此部分就不會執(zhí)行了~~~~
processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);
return returnValue;
}
// 緩存屬性的上下文們。每個(gè)方法可以對應(yīng)多個(gè)上下文~~~
private class CacheOperationContexts {
// 因?yàn)榉椒ㄉ峡梢詷?biāo)注多個(gè)注解
// 需要注意的是它的key是Class呜达,而CacheOperation的子類也就那三個(gè)哥們而已~
private final MultiValueMap<Class<? extends CacheOperation>, CacheOperationContext> contexts;
// 是否要求同步執(zhí)行谣蠢,默認(rèn)值是false
private final boolean sync;
public CacheOperationContexts(Collection<? extends CacheOperation> operations, Method method, Object[] args, Object target, Class<?> targetClass) {
this.contexts = new LinkedMultiValueMap<>(operations.size());
for (CacheOperation op : operations) {
this.contexts.add(op.getClass(), getOperationContext(op, method, args, target, targetClass));
}
// sync這個(gè)屬性雖然不怎么使用,但determineSyncFlag這個(gè)方法可以看一下
this.sync = determineSyncFlag(method);
}
public Collection<CacheOperationContext> get(Class<? extends CacheOperation> operationClass) {
Collection<CacheOperationContext> result = this.contexts.get(operationClass);
return (result != null ? result : Collections.emptyList());
}
public boolean isSynchronized() {
return this.sync;
}
// 因?yàn)橹挥蠤Cacheable有sync屬性,所以只需要看CacheableOperation即可
private boolean determineSyncFlag(Method method) {
List<CacheOperationContext> cacheOperationContexts = this.contexts.get(CacheableOperation.class);
if (cacheOperationContexts == null) { // no @Cacheable operation at all
return false;
}
boolean syncEnabled = false;
// 單反只要有一個(gè)@Cacheable的sync=true了眉踱,那就為true 并且下面還有檢查邏輯
for (CacheOperationContext cacheOperationContext : cacheOperationContexts) {
if (((CacheableOperation) cacheOperationContext.getOperation()).isSync()) {
syncEnabled = true;
break;
}
}
// 執(zhí)行sync=true的檢查邏輯
if (syncEnabled) {
// 人話解釋:sync=true時(shí)候挤忙,不能還有其它的緩存操作 也就是說@Cacheable(sync=true)的時(shí)候只能單獨(dú)使用
if (this.contexts.size() > 1) {
throw new IllegalStateException("@Cacheable(sync=true) cannot be combined with other cache operations on '" + method + "'");
}
// 人話解釋:@Cacheable(sync=true)時(shí),多個(gè)@Cacheable也是不允許的
if (cacheOperationContexts.size() > 1) {
throw new IllegalStateException("Only one @Cacheable(sync=true) entry is allowed on '" + method + "'");
}
// 拿到唯一的一個(gè)@Cacheable
CacheOperationContext cacheOperationContext = cacheOperationContexts.iterator().next();
CacheableOperation operation = (CacheableOperation) cacheOperationContext.getOperation();
// 人話解釋:@Cacheable(sync=true)時(shí)谈喳,cacheName只能使用一個(gè)
if (cacheOperationContext.getCaches().size() > 1) {
throw new IllegalStateException("@Cacheable(sync=true) only allows a single cache on '" + operation + "'");
}
// 人話解釋:sync=true時(shí)册烈,unless屬性是不支持的~~~并且是不能寫的
if (StringUtils.hasText(operation.getUnless())) {
throw new IllegalStateException("@Cacheable(sync=true) does not support unless attribute on '" + operation + "'");
}
return true; // 只有校驗(yàn)都通過后,才返回true
}
return false;
}
}
...
}
以上婿禽,攔截器實(shí)現(xiàn)了Spring Cache
處理注解緩存的執(zhí)行的核心步驟赏僧,個(gè)人建議上述代碼可多讀幾遍,其義自見扭倾。
處理緩存注解的步驟總結(jié)
Spring Cache是Spring框架的核心模塊之一次哈,不可謂不重要。用了好幾篇文章專門來講解使用吆录、分析原理。下面按照正常的思路琼牧,我把Spring處理的步驟總結(jié)如下:
-
CacheOperation
封裝了@CachePut
恢筝、@Cacheable
、@CacheEvict
(下稱三大緩存注解)的屬性信息巨坊,以便于攔截的時(shí)候能直接操作此對象來執(zhí)行邏輯撬槽。 1. 解析三大注解到CacheOperation
的過程是由CacheAnnotationParser
完成的 -
CacheAnnotationSource
代表緩存屬性源,非常非常重要的一個(gè)概念趾撵。它提供接口方法來獲取目標(biāo)方法的CacheOperation
集合侄柔。由上可知,這個(gè)具體工作是委托給CacheAnnotationParser
去完成的 -
BeanFactoryCacheOperationSourceAdvisor
它代表增強(qiáng)器占调,至于需要增強(qiáng)哪些類呢暂题??究珊?就是看有沒有存在CacheOperation
屬性的方法 -
CacheInterceptor
實(shí)現(xiàn)了MethodInterceptor
接口薪者,在Spring AOP
中實(shí)現(xiàn)對執(zhí)行方法的攔截。在調(diào)用invoke
執(zhí)行目標(biāo)方法前后剿涮,通過CacheAnnotationSource
獲取到方法所有的緩存操作屬性言津,從而一個(gè)個(gè)的執(zhí)行 - 執(zhí)行的時(shí)候,每一個(gè)
CacheOperation
最后被封裝成了CacheOperationContext
取试,而CacheOperationContext
最終通過CacheResolver
解析出緩存對象Cache
(可能是多個(gè)) - 最后最后最后悬槽,
CacheInterceptor
調(diào)用其父類AbstractCacheInvoker
執(zhí)行對應(yīng)的doPut
/doGet
/doEvict
/doClear
等等。(可以處理執(zhí)行異常)
CacheProxyFactoryBean:手動實(shí)現(xiàn)Cache功能
其實(shí)ProxyFactoryBean
的設(shè)計(jì)模式在Spring AOP中已經(jīng)非常不陌生了:【小家Spring】面向切面編程Spring AOP創(chuàng)建代理的方式:ProxyFactoryBean瞬浓、ProxyFactory初婆、AspectJProxyFactory(JDK Proxy和CGLIB)
如下截圖,Spring內(nèi)有非常多的xxxProxyFactoryBean
的實(shí)現(xiàn):
如果說把@EnableCaching
稱為自動模式的話,那使用CacheProxyFactoryBean
就完全是手動檔烟逊。話不多說渣窜,此處給個(gè)使用Demo就收場了:
@Configuration
public class RootConfig {
@Bean
public CacheProxyFactoryBean cacheProxyFactoryBean() {
CacheProxyFactoryBean proxyFactoryBean = new CacheProxyFactoryBean();
// 使用AnnotationCacheOperationSource來識別三大注解緩存
proxyFactoryBean.setCacheOperationSources(new AnnotationCacheOperationSource());
// 設(shè)置需要代理的目標(biāo)類
CacheDemoService cacheDemoService = new CacheDemoServiceImpl();
proxyFactoryBean.setTarget(cacheDemoService);
//proxyFactoryBean.setProxyInterfaces();
// 設(shè)置個(gè)性化的一些東西
CacheManager cacheManager = new ConcurrentMapCacheManager();
proxyFactoryBean.setCacheManager(cacheManager);
//proxyFactoryBean.setKeyGenerator();
//proxyFactoryBean.setCacheResolver();
return proxyFactoryBean;
}
}
//@Service // 因?yàn)槭褂昧薈acheProxyFactoryBean手動額皮質(zhì),此處請不要再被掃描進(jìn)去宪躯,否則容器內(nèi)就出現(xiàn)兩個(gè)這樣的Bean了
public class CacheDemoServiceImpl implements CacheDemoService {
@Cacheable(cacheNames = "demoCache", key = "#id")
@Override
public Object getFromDB(Integer id) {
System.out.println("模擬去db查詢~~~" + id);
return "hello cache...";
}
}
測試:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {RootConfig.class, CacheConfig.class})
public class TestSpringBean {
@Autowired
private CacheDemoService cacheDemoService;
@Test
public void test1() {
cacheDemoService.getFromDB(1);
cacheDemoService.getFromDB(1);
}
}
打印結(jié)果:
模擬去db查詢~~~1</pre>
只輸出一套日志:緩存生效
此示例中
@EnableCaching
可不是打開狀態(tài)哦乔宿,但我們依然能夠使用手動檔讓緩存生效。 使用手動檔访雪,我們可以很方便的使用NameMatchCacheOperationSource
來根據(jù)方法名匹配~~~
緩存注解使用案例
關(guān)于緩存注解的常規(guī)使用案例详瑞,我覺得本文沒有必要介紹。 接下來主要講解一些特殊的使用:
若方法返回值為null臣缀,還會緩存嗎坝橡?
比如上例返回值改為null:
@Service
public class CacheDemoServiceImpl implements CacheDemoService {
@Cacheable(cacheNames = "demoCache", key = "#id")
@Override
public Object getFromDB(Integer id) {
System.out.println("模擬去db查詢~~~" + id);
//return "hello cache...";
return null;
}
}
執(zhí)行單測:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {RootConfig.class, CacheConfig.class})
public class TestSpringBean {
@Autowired
private CacheDemoService cacheDemoService;
@Autowired
private CacheManager cacheManager;
@Test
public void test1() {
cacheDemoService.getFromDB(1);
cacheDemoService.getFromDB(1);
System.out.println("----------驗(yàn)證緩存是否生效----------");
Cache cache = cacheManager.getCache("demoCache");
System.out.println(cache);
System.out.println(cache.get(1, String.class));
}
}
結(jié)果打印:
模擬去db查詢~~~1
----------驗(yàn)證緩存是否生效----------
org.springframework.cache.concurrent.ConcurrentMapCache@15f2eda3
null
結(jié)論是:默認(rèn)情況下精置,返回值是null也是會緩存的(第二次過來就不會再查詢了)计寇。通過一個(gè)斷點(diǎn)會更清晰:
[圖片上傳失敗...(image-cf500b-1640332873234)]
但假如修改緩存注解如下:
// 注意:unless 是非的意思
@Cacheable(cacheNames = "demoCache", key = "#id",unless = "#result == null")</pre>
運(yùn)行打印如下:
模擬去db查詢~~~1
模擬去db查詢~~~1
----------驗(yàn)證緩存是否生效----------
org.springframework.cache.concurrent.ConcurrentMapCache@6a282fdd
null
查了兩次DB,證明此時(shí)返回null就不會再緩存了脂倦,unless生效番宁。
倘若修改配置如下:
@Bean
public CacheManager cacheManager() {
ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
cacheManager.setAllowNullValues(false);
return cacheManager;
}
運(yùn)行則報(bào)錯(cuò):
java.lang.IllegalArgumentException: Cache 'demoCache' is configured to not allow null values but null was provided
一般情況下,不建議這么設(shè)置赖阻,因?yàn)橐话愣际窃试S緩存null值的蝶押。
@Cacheable
注解sync=true
的效果
在多線程環(huán)境下,某些操作可能使用相同參數(shù)同步調(diào)用(相同的key)火欧。默認(rèn)情況下棋电,緩存不鎖定任何資源,可能導(dǎo)致多次計(jì)算苇侵,而違反了緩存的目的赶盔。對于這些特定的情況,屬性 sync 可以指示底層將緩存鎖住榆浓,使只有一個(gè)線程可以進(jìn)入計(jì)算招刨,而其他線程堵塞,直到返回結(jié)果更新到緩存中(Spring4.3提供的)哀军。
下面給個(gè)例子直接看看效果就成:
@Cacheable(cacheNames = "demoCache", key = "#id")
測試Demo(多線程):
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {RootConfig.class, CacheConfig.class})
public class TestSpringBean {
@Autowired
private CacheDemoService cacheDemoService;
@Test
public void test1() throws InterruptedException {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
cacheDemoService.getFromDB(1);
}).start();
}
// 保證main線程不要這么快結(jié)束(否則沒有日志結(jié)果的~~~)
TimeUnit.SECONDS.sleep(10);
}
}
打印結(jié)果:
模擬去db查詢~~~1
模擬去db查詢~~~1
模擬去db查詢~~~1
模擬去db查詢~~~1
模擬去db查詢~~~1</pre>
打印可能5次沉眶、可能6次不確定。但是若我們sync=true了呢杉适?
@Cacheable(cacheNames = "demoCache", key = "#id", sync = true)
再次運(yùn)行測試打印結(jié)果如下:
模擬去db查詢~~~1
永遠(yuǎn)只會打印一次谎倔。 所以在高并發(fā)環(huán)境下,我個(gè)人是十分建議開啟此同步開關(guān)的猿推,至于非高并發(fā)片习,無所謂啦~
注解可重復(fù)標(biāo)注嗎捌肴?
因?yàn)樵创a都分析了,所以此處看結(jié)論即可藕咏。
@CachePut(cacheNames = "demoCache", key = "#id") // 不同的注解可以標(biāo)注多個(gè)
//@Cacheable(cacheNames = "demoCache", key = "#id") // 相同注解標(biāo)注兩個(gè)是不行的 因?yàn)樗⒉皇茾Repeatable的
@Cacheable(cacheNames = "demoCache", key = "#id")
@Override
public Object getFromDB(Integer id) {
System.out.println("模擬去db查詢~~~" + id);
return "hello cache...";
}
不同的注解可以標(biāo)注多個(gè)状知,且都能生效。若需要相同注解標(biāo)注多個(gè)等更復(fù)雜的場景孽查,推薦使用@Caching
注解
如何
給緩存注解設(shè)置專用的key生成器饥悴、緩存管理器等等
標(biāo)準(zhǔn)寫法是這樣的:
@EnableCaching
@Configuration
public class CacheConfig implements CachingConfigurer {
@Override
public CacheResolver cacheResolver() {
return null;
}
@Override
public KeyGenerator keyGenerator() {
return null;
}
@Override
public CacheErrorHandler errorHandler() {
return null;
}
}
實(shí)現(xiàn)對應(yīng)的方法,可以new一個(gè)對象返回盲再,也可以用容器內(nèi)的西设。
大多數(shù)情況下我們都不需要特別的指定緩存注解使用的管理器,因?yàn)樗约簳ト萜骼镎摇?但是答朋,但是贷揽,但是當(dāng)你使用了多套緩存時(shí),我還是建議顯示的指定的梦碗。
總結(jié)
本篇文章相對較長禽绪,因?yàn)閺脑硖幧疃确治隽?code>Spring Cache的執(zhí)行過程,希望能幫助到大家做到心里有數(shù)洪规,從而更加健康的書寫代碼和擴(kuò)展功能印屁。
關(guān)于緩存注解這塊,我相信很多人都有一個(gè)痛點(diǎn):注解并不支持設(shè)置過期時(shí)間淹冰。其實(shí)我想說,如果你看了上篇文章就知道巨柒,這個(gè)Spring它做不了樱拴,因?yàn)樗]有對Expire
進(jìn)行抽象。 但是Spring做不了不代表我們自己做不了洋满,因此有興趣的同學(xué)可以在此基礎(chǔ)上晶乔,擴(kuò)展出可以自定義超時(shí)時(shí)間的能力~~~~