Spring源碼解析-Spring Cache

Spring版本

5.0.9.RELEASE

背景

最近正好在做緩存相關(guān)的項(xiàng)目,用到了Spring Cache搭配Redis實(shí)現(xiàn),本著“知其然亦須知其所以然”的精神,研究了一下Spring Cache的源碼,記錄一下,以便深入理解。本文皆是基于自身理解撰寫,能力有限,若有錯誤之處,不妨賜教。

1. Spring Cache提供的注解

1.1 Cacheable

被該注解修飾的方法在執(zhí)行之前,會去緩存中查找是都存在對應(yīng)key的緩存,若存在,則不執(zhí)行方法,直接返回緩存結(jié)果,否則,執(zhí)行方法并將結(jié)果緩存

1.1.1 屬性

  • value:緩存名稱,cacheNames的別名
  • cacheNames:緩存名稱,value的別名,當(dāng)RedisCacheConfiguration中的usePrefix配置為true時,作為key前綴
  • key:緩存的key,支持SpEL表達(dá)式,未定義默認(rèn)的keyGenerator的情況下,使用方法的所有參數(shù)作為key,支持:
    1. #root.method:引用方法
    2. #root.target:引用目標(biāo)對象
    3. #root.methodName:引用方法名
    4. #root.targetClass:來引用目標(biāo)類
    5. #args[1]或者#a1或者#p1: 引用參數(shù),也可以直接使用#參數(shù)名
  • keyGenerator:自定義的key生成器
  • cacheManager:自定義的緩存管理器
  • cacheResolver:自定義的緩存解析器
  • condition:緩存的前提條件,支持SpEL表達(dá)式,默認(rèn)值為"",意味著方法執(zhí)行結(jié)果永遠(yuǎn)會被緩存起來
  • unless:拒絕緩存的條件,支持SpEL表達(dá)式,默認(rèn)為"",意味著不會拒絕緩存方法執(zhí)行的結(jié)果,與condition不同的是,該表達(dá)式在方法執(zhí)行結(jié)束之后生效,并且可以通過#result來引用執(zhí)行后的結(jié)果
  • sync:是否開啟同步,開啟后僅允許被注解方法有且只有一個@Cacheable注解,并且不支持unless,并且不能同時擁有其他緩存注解,后文的源碼解讀中將體現(xiàn)sync=true的限制

1.2 CacheConfig

提供一個全局的配置,具體字段在@Cacheable中已經(jīng)說明,此處不贅述

1.3 CacheEvict

字段與@Cacheable基本一致,多了以下幾個字段:

1.3.1 屬性

  • allEntries:默認(rèn)false,代表按照key刪除對應(yīng)緩存,當(dāng)指定為true,代表刪除對應(yīng)cacheNames下的全部緩存
  • beforeInvocation:是否在方法調(diào)用前觸發(fā)刪除邏輯,默認(rèn)false,代表方法執(zhí)行之后再刪除緩存,若方法執(zhí)行過程中拋出異常,不執(zhí)行刪除邏輯,設(shè)置為true,代表方法執(zhí)行之前刪除緩存,若方法執(zhí)行過程中拋出異常,不影響刪除邏輯

1.4 CachePut

@Cacheable不同的是,被該注解修飾的方法在執(zhí)行之前,不會去緩存中檢查是否存在對應(yīng)key的緩存,而是直接執(zhí)行方法并將結(jié)果緩存

1.5 Caching

@Cacheable、@CachePut@CacheEvict的組合注解,方便我們對多個cache執(zhí)行相應(yīng)邏輯

1.6 EnableCaching

開啟cache

1.6.1 屬性

  • proxyTargetClass:僅當(dāng)mode設(shè)置為Proxy時生效,為false代表jdk代理,為true代表cglib代理,設(shè)置為true同時會影響所有需要代理的且由spring管理的bean的代理模式,如被Transactional修飾
  • mode:Proxy:代理模式,AspectJ:切面模式,默認(rèn)值Proxy
  • order:同個切入點(diǎn)有多個切面的時候的執(zhí)行順序

2. 源碼解讀

很明顯,我們可以從EnableCaching作為源碼閱讀突破口,可以看到:

@Import(CachingConfigurationSelector.class)

EnableCaching中引入了CachingConfigurationSelector

@Import用于引入一個或多個Configuration,等效于在xml文件中的<import/>標(biāo)簽

結(jié)合EnableCachingmode默認(rèn)為Proxy模式,明顯CachingConfigurationSelector中的核心方法為:

    /**
     * Returns {@link ProxyCachingConfiguration} or {@code AspectJCachingConfiguration}
     * for {@code PROXY} and {@code ASPECTJ} values of {@link EnableCaching#mode()},
     * respectively. Potentially includes corresponding JCache configuration as well.
     */
    @Override
    public String[] selectImports(AdviceMode adviceMode) {
        switch (adviceMode) {
            case PROXY:
                return getProxyImports();
            case ASPECTJ:
                return getAspectJImports();
            default:
                return null;
        }
    }

    /**
     * Return the imports to use if the {@link AdviceMode} is set to {@link AdviceMode#PROXY}.
     * <p>Take care of adding the necessary JSR-107 import if it is available.
     */
    private String[] getProxyImports() {
        List<String> result = new ArrayList<>(3);
        result.add(AutoProxyRegistrar.class.getName());
        result.add(ProxyCachingConfiguration.class.getName());
        if (jsr107Present && jcacheImplPresent) {
            result.add(PROXY_JCACHE_CONFIGURATION_CLASS);
        }
        return StringUtils.toStringArray(result);
    }

getProxyImports方法中,我們可以看到此處引入了AutoProxyRegistrarProxyCachingConfiguration倆個類,看名字應(yīng)該是自動代理注冊以及代理緩存配置,先看看AutoProxyRegistrar

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        boolean candidateFound = false;
        Set<String> annoTypes = importingClassMetadata.getAnnotationTypes();
        for (String annoType : annoTypes) {
            AnnotationAttributes candidate = AnnotationConfigUtils.attributesFor(importingClassMetadata, annoType);
            if (candidate == null) {
                continue;
            }
            Object mode = candidate.get("mode");
            Object proxyTargetClass = candidate.get("proxyTargetClass");
            if (mode != null && proxyTargetClass != null && AdviceMode.class == mode.getClass() &&
                    Boolean.class == proxyTargetClass.getClass()) {
                candidateFound = true;
                if (mode == AdviceMode.PROXY) {
                    AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
                    if ((Boolean) proxyTargetClass) {
                        AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
                        return;
                    }
                }
            }
        }
        if (!candidateFound && logger.isWarnEnabled()) {
            String name = getClass().getSimpleName();
            logger.warn(String.format("%s was imported but no annotations were found " +
                    "having both 'mode' and 'proxyTargetClass' attributes of type " +
                    "AdviceMode and boolean respectively. This means that auto proxy " +
                    "creator registration and configuration may not have occurred as " +
                    "intended, and components may not be proxied as expected. Check to " +
                    "ensure that %s has been @Import'ed on the same class where these " +
                    "annotations are declared; otherwise remove the import of %s " +
                    "altogether.", name, name, name));
        }
    }

我們開啟debug,啟動項(xiàng)目,可以看到,該方法的入?yún)?code>importingClassMetadata傳入的是我們的啟動類:

Application

那么:

Set<String> annoTypes = importingClassMetadata.getAnnotationTypes();

這一句很明顯就是獲取Application上面的注解:

EnableCaching

這里我們設(shè)置條件斷點(diǎn),只查看EnableCaching的執(zhí)行流程,核心代碼在于:

AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
if ((Boolean) proxyTargetClass) {
    AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
    return;
}

首先使用jdk代理進(jìn)行注冊,如果proxyTargetClass設(shè)置為true,則轉(zhuǎn)化為cglib代理。
進(jìn)入registerAutoProxyCreatorIfNecessary方法,層層追溯,可以看到核心代碼為:

    @Nullable
    private static BeanDefinition registerOrEscalateApcAsRequired(Class<?> cls, BeanDefinitionRegistry registry,
            @Nullable Object source) {

        Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
                
        if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
            BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
            if (!cls.getName().equals(apcDefinition.getBeanClassName())) {
                int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());
                int requiredPriority = findPriorityForClass(cls);
                if (currentPriority < requiredPriority) {
                    apcDefinition.setBeanClassName(cls.getName());
                }
            }
            return null;
        }

        RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
        beanDefinition.setSource(source);
        beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
        beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
        return beanDefinition;
    }

首先判斷registry中是否包含AUTO_PROXY_CREATOR_BEAN_NAME對應(yīng)的beanDefinition

  • 包含
    獲取當(dāng)前beanDefinition,如果和請求參數(shù)cls不是同一個bean,則根據(jù)優(yōu)先級判斷是否需要替換當(dāng)前bean,那么優(yōu)先級是如何定義的呢,點(diǎn)擊任意findPriorityForClass方法:
private static int findPriorityForClass(Class<?> clazz) {
        return APC_PRIORITY_LIST.indexOf(clazz);
}

可以看到有一個集合APC_PRIORITY_LIST,查找初始化代碼,可以看到其是在靜態(tài)代碼塊中初始化了三個AutoProxyCreator,且通過index表明其優(yōu)先級:

    static {
        APC_PRIORITY_LIST.add(InfrastructureAdvisorAutoProxyCreator.class);
        APC_PRIORITY_LIST.add(AspectJAwareAdvisorAutoProxyCreator.class);
        APC_PRIORITY_LIST.add(AnnotationAwareAspectJAutoProxyCreator.class);
    }
  • 不包含
    初始化一個BeanDefinition并注冊

現(xiàn)在我們回過頭來看看ProxyCachingConfiguration

/*
 * Copyright 2002-2018 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.cache.annotation;

import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.cache.config.CacheManagementConfigUtils;
import org.springframework.cache.interceptor.BeanFactoryCacheOperationSourceAdvisor;
import org.springframework.cache.interceptor.CacheInterceptor;
import org.springframework.cache.interceptor.CacheOperationSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Role;

/**
 * {@code @Configuration} class that registers the Spring infrastructure beans necessary
 * to enable proxy-based annotation-driven cache management.
 *
 * @author Chris Beams
 * @author Juergen Hoeller
 * @since 3.1
 * @see EnableCaching
 * @see CachingConfigurationSelector
 */
@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyCachingConfiguration extends AbstractCachingConfiguration {

    @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;
    }

    @Bean
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    public CacheOperationSource cacheOperationSource() {
        return new AnnotationCacheOperationSource();
    }

    @Bean
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    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;
    }

}

cacheAdvisor方法中,我們可以看到設(shè)置了cacheOperationSourcecacheInterceptor,而cacheOperationSource則是AnnotationCacheOperationSource實(shí)例,查看AnnotationCacheOperationSource

    /**
     * Create a default AnnotationCacheOperationSource, supporting public methods
     * that carry the {@code Cacheable} and {@code CacheEvict} annotations.
     */
    public AnnotationCacheOperationSource() {
        this(true);
    }

    /**
     * Create a default {@code AnnotationCacheOperationSource}, supporting public methods
     * that carry the {@code Cacheable} and {@code CacheEvict} annotations.
     * @param publicMethodsOnly whether to support only annotated public methods
     * typically for use with proxy-based AOP), or protected/private methods as well
     * (typically used with AspectJ class weaving)
     */
    public AnnotationCacheOperationSource(boolean publicMethodsOnly) {
        this.publicMethodsOnly = publicMethodsOnly;
        this.annotationParsers = Collections.singleton(new SpringCacheAnnotationParser());
    }

可以看到,這種情況下只支持public方法的緩存,同時還可以看到還有一個注解解析器SpringCacheAnnotationParser,核心代碼在于:

    @Nullable
    private Collection<CacheOperation> parseCacheAnnotations(
            DefaultCacheConfig cachingConfig, AnnotatedElement ae, boolean localOnly) {

        Collection<CacheOperation> ops = null;

        Collection<Cacheable> cacheables = (localOnly ? AnnotatedElementUtils.getAllMergedAnnotations(ae, Cacheable.class) :
                AnnotatedElementUtils.findAllMergedAnnotations(ae, Cacheable.class));
        if (!cacheables.isEmpty()) {
            ops = lazyInit(null);
            for (Cacheable cacheable : cacheables) {
                ops.add(parseCacheableAnnotation(ae, cachingConfig, cacheable));
            }
        }

        Collection<CacheEvict> evicts = (localOnly ? AnnotatedElementUtils.getAllMergedAnnotations(ae, CacheEvict.class) :
                AnnotatedElementUtils.findAllMergedAnnotations(ae, CacheEvict.class));
        if (!evicts.isEmpty()) {
            ops = lazyInit(ops);
            for (CacheEvict evict : evicts) {
                ops.add(parseEvictAnnotation(ae, cachingConfig, evict));
            }
        }

        Collection<CachePut> puts = (localOnly ? AnnotatedElementUtils.getAllMergedAnnotations(ae, CachePut.class) :
                AnnotatedElementUtils.findAllMergedAnnotations(ae, CachePut.class));
        if (!puts.isEmpty()) {
            ops = lazyInit(ops);
            for (CachePut put : puts) {
                ops.add(parsePutAnnotation(ae, cachingConfig, put));
            }
        }

        Collection<Caching> cachings = (localOnly ? AnnotatedElementUtils.getAllMergedAnnotations(ae, Caching.class) :
                AnnotatedElementUtils.findAllMergedAnnotations(ae, Caching.class));
        if (!cachings.isEmpty()) {
            ops = lazyInit(ops);
            for (Caching caching : cachings) {
                Collection<CacheOperation> cachingOps = parseCachingAnnotation(ae, cachingConfig, caching);
                if (cachingOps != null) {
                    ops.addAll(cachingOps);
                }
            }
        }

        return ops;
    }

通過這段代碼,我們不難看出,其功能在于解析@Cacheable@CacheEvict、@CachePut注解,并將其包裝成通用的CacheOperation,方便我們后續(xù)對其處理(后面的源碼中很多地方都會有CacheOperation的出場機(jī)會)
其中,localOnly代表如果被注解接口聲明和實(shí)現(xiàn)同時存在緩存注解,那么,實(shí)現(xiàn)上的注解將會覆蓋接口聲明的注解:

// More than one operation found -> local declarations override interface-declared ones...

疑問:為啥是覆蓋而不是整合呢?待補(bǔ)充


再回過頭來看看CacheInterceptor

public class CacheInterceptor extends CacheAspectSupport implements MethodInterceptor, Serializable {

    @Override
    @Nullable
    public Object invoke(final MethodInvocation invocation) throws Throwable {
        Method method = invocation.getMethod();

        CacheOperationInvoker aopAllianceInvoker = () -> {
            try {
                return invocation.proceed();
            }
            catch (Throwable ex) {
                throw new CacheOperationInvoker.ThrowableWrapper(ex);
            }
        };

        try {
            return execute(aopAllianceInvoker, invocation.getThis(), method, invocation.getArguments());
        }
        catch (CacheOperationInvoker.ThrowableWrapper th) {
            throw th.getOriginal();
        }
    }

}

可以看到CacheInterceptor主要的實(shí)現(xiàn)方法是execute,該方法的定義如下:

execute(CacheOperationInvoker invoker, Object target, Method method, Object[] args)

參數(shù)解析如下:

  • invoker:這里我們傳入的是一個匿名方法,用于執(zhí)行被注解的方法
  • target:被注解對象
  • method:被注解的方法
  • args:被注解方法的參數(shù)
    execute方法實(shí)現(xiàn)如下:
    @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)初始化,initialized默認(rèn)為false,在afterSingletonsInstantiated方法中被賦值為true
        if (this.initialized) {
            // 獲取被注解對象類型
            Class<?> targetClass = getTargetClass(target);
            CacheOperationSource cacheOperationSource = getCacheOperationSource();
            if (cacheOperationSource != null) {
                // 獲取注解在方法上的注解list
                Collection<CacheOperation> operations = cacheOperationSource.getCacheOperations(method, targetClass);
                if (!CollectionUtils.isEmpty(operations)) {
                    return execute(invoker, method,
                            new CacheAspectSupport.CacheOperationContexts(operations, method, args, target, targetClass));
                }
            }
        }
        // 緩存處理完畢之后,調(diào)用被注解方法執(zhí)行邏輯
        return invoker.invoke();
    }

可以看到,此處調(diào)用了execute的重載方法,這里封裝了一個緩存操作上下文對象作為參數(shù),我們先看一下CacheOperationContexts這個內(nèi)部類對象:

    private class CacheOperationContexts {
        // 緩存操作(@Cacheable、@CacheEvict等)和上下文環(huán)境的映射map
        private final MultiValueMap<Class<? extends CacheOperation>, CacheAspectSupport.CacheOperationContext> contexts;

        // 是否開啟同步
        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));
            }
            this.sync = determineSyncFlag(method);
        }

        public Collection<CacheAspectSupport.CacheOperationContext> get(Class<? extends CacheOperation> operationClass) {
            Collection<CacheAspectSupport.CacheOperationContext> result = this.contexts.get(operationClass);
            return (result != null ? result : Collections.emptyList());
        }

        public boolean isSynchronized() {
            return this.sync;
        }

        // 解析sync的值
        private boolean determineSyncFlag(Method method) {
            // 獲取@Cacheable的上下文
            List<CacheAspectSupport.CacheOperationContext> cacheOperationContexts = this.contexts.get(CacheableOperation.class);
            if (cacheOperationContexts == null) {  // no @Cacheable operation at all
                return false;
            }
            boolean syncEnabled = false;
            // 若是存在多個@Cacheable,如果其中一個sync標(biāo)識為true,則認(rèn)為是同步操作
            for (CacheAspectSupport.CacheOperationContext cacheOperationContext : cacheOperationContexts) {
                if (((CacheableOperation) cacheOperationContext.getOperation()).isSync()) {
                    syncEnabled = true;
                    break;
                }
            }
            if (syncEnabled) {
                // 不允許存在任何該同步注解之外的緩存注解
                if (this.contexts.size() > 1) {
                    throw new IllegalStateException(
                            "@Cacheable(sync=true) cannot be combined with other cache operations on '" + method + "'");
                }
                // 不允許存在任何該同步注解之外的@Cacheable注解
                if (cacheOperationContexts.size() > 1) {
                    throw new IllegalStateException(
                            "Only one @Cacheable(sync=true) entry is allowed on '" + method + "'");
                }
                // 拿到同步注解@Cacheable對象
                CacheAspectSupport.CacheOperationContext cacheOperationContext = cacheOperationContexts.iterator().next();
                CacheableOperation operation = (CacheableOperation) cacheOperationContext.getOperation();
                // 該同步注解的cacheNames屬性只能有一個值
                if (cacheOperationContext.getCaches().size() > 1) {
                    throw new IllegalStateException(
                            "@Cacheable(sync=true) only allows a single cache on '" + operation + "'");
                }
                // @Cacheable處提到的開啟同步的情況下,不支持unless
                if (StringUtils.hasText(operation.getUnless())) {
                    throw new IllegalStateException(
                            "@Cacheable(sync=true) does not support unless attribute on '" + operation + "'");
                }
                return true;
            }
            return false;
        }
    }

回過頭來看看execute的重載方法:

Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts)

該方法內(nèi)部實(shí)現(xiàn)主要分成倆部分,一部分是對同步的處理,另一部分是非同步的處理

  • 同步:
    // CacheOperationContexts#determineSyncFlag解析的同步結(jié)果體現(xiàn)在這個條件判斷中
    if (contexts.isSynchronized()) {
        // 獲取同步的@Cacheable注解
        CacheAspectSupport.CacheOperationContext context = contexts.get(CacheableOperation.class).iterator().next();
        // 判斷是否滿足@Cacheable注解的conditional屬性
        if (isConditionPassing(context, CacheOperationExpressionEvaluator.NO_RESULT)) {
            // 生成key
            Object key = generateKey(context, CacheOperationExpressionEvaluator.NO_RESULT);
            // 獲取方法的cacheName屬性,同步情況下只會有一個
            Cache cache = context.getCaches().iterator().next();
            try {
                // 執(zhí)行緩存邏輯,同步交由緩存提供商實(shí)現(xiàn),wrapCacheValue和unwrapReturnValue都是對返回值做一下Optional的處理,不展開
                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
            // 不滿足緩存條件,直接調(diào)用被注解方法
            return invokeOperation(invoker);
        }
    }

其中,cache#get方法在redis中的實(shí)現(xiàn)如下:

    // 使用 synchronized 保證同步
    @Override
    @SuppressWarnings("unchecked")
    public synchronized <T> T get(Object key, Callable<T> valueLoader) {

        // 從redis中讀取緩存結(jié)果
        Cache.ValueWrapper result = get(key);

        // 緩存結(jié)果非空,直接以緩存結(jié)果作為返回值
        if (result != null) {
            return (T) result.get();
        }

        // 否則執(zhí)行方法,獲取執(zhí)行結(jié)果,并緩存
        T value = valueFromLoader(key, valueLoader);
        put(key, value);
        return value;
    }

  • 不同步:
    // Process any early evictions
    // 處理beforeInvocation為true的@CacheEvict,該部分需要在方法調(diào)用之前處理
    processCacheEvicts(contexts.get(CacheEvictOperation.class), true,
    CacheOperationExpressionEvaluator.NO_RESULT);

    // Check if we have a cached item matching the conditions
    // 從緩存中獲取緩存數(shù)據(jù)
    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í)行緩存寫入邏輯,這里僅僅只是收集應(yīng)該被寫入緩存的@Cacheable數(shù)據(jù)到cachePutRequests中,真正的寫入緩存操作在后續(xù)的apply中
    if (cacheHit == null) {
        collectPutRequests(contexts.get(CacheableOperation.class),
                CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);
    }

    // 應(yīng)該被緩存的值
    Object cacheValue;
    // 方法返回值,與cacheValue的區(qū)別在于可能是一個Optional對象
    Object returnValue;

    // @CachePut注解表示無視緩存結(jié)果,強(qiáng)制執(zhí)行方法并將結(jié)果緩存,所以這里必須判斷是否有該注解
    if (cacheHit != null && !hasCachePut(contexts)) {
        // If there are no put requests, just use the cache hit
        // 命中緩存,且沒有@CachePut注解的情況則直接返回緩存結(jié)果
        cacheValue = cacheHit.get();
        returnValue = wrapCacheValue(method, cacheValue);
    }
    else {
        // Invoke the method if we don't have a cache hit
        // 未命中緩存,則執(zhí)行方法邏輯,獲取執(zhí)行結(jié)果和緩存結(jié)果
        returnValue = invokeOperation(invoker);
        cacheValue = unwrapReturnValue(returnValue);
    }

    // Collect any explicit @CachePuts
    // 收集應(yīng)該被寫入緩存的@CachePut數(shù)據(jù)到cachePutRequests中
    collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);

    // Process any collected put requests, either from @CachePut or a @Cacheable miss
    // 此處是@Cacheable和@CachePut的緩存真正寫入邏輯
    for (CachePutRequest cachePutRequest : cachePutRequests) {
        cachePutRequest.apply(cacheValue);
    }

    // Process any late evictions
    // 執(zhí)行后續(xù)的@CacheEvict邏輯
    processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);

    return returnValue;

以上我們講解了緩存的處理主流程,接下來詳細(xì)看看每個子節(jié)點(diǎn)的具體實(shí)現(xiàn)邏輯:

  • processCacheEvicts
    private void processCacheEvicts(
            Collection<CacheOperationContext> contexts, boolean beforeInvocation, @Nullable Object result) {

        for (CacheOperationContext context : contexts) {
            CacheEvictOperation operation = (CacheEvictOperation) context.metadata.operation;
            if (beforeInvocation == operation.isBeforeInvocation() && isConditionPassing(context, result)) {
                performCacheEvict(context, operation, result);
            }
        }
    }

核心點(diǎn)在于通過isConditionPassing方法判斷是否符合刪除前提條件,isConditionPassing中涉及到SpEL表達(dá)式的邏輯,不是本文的重點(diǎn),此處不予展開。若滿足前提條件,執(zhí)行performCacheEvict方法來進(jìn)行緩存刪除邏輯:

    private void performCacheEvict(
            CacheAspectSupport.CacheOperationContext context, CacheEvictOperation operation, @Nullable Object result) {

        Object key = null;
        for (Cache cache : context.getCaches()) {
            // cacheWide就是allEntries,只不過在CacheEvictOperation中名字不一樣罷了
            if (operation.isCacheWide()) {
                // 打印日志
                logInvalidating(context, operation, null);
                // 整塊緩存刪除
                doClear(cache);
            }
            else {
                if (key == null) {
                    key = generateKey(context, result);
                }
                logInvalidating(context, operation, key);
                // 根據(jù)key刪除對應(yīng)緩存
                doEvict(cache, key);
            }
        }
    }

接下來分別看看doCleardoEvict方法:
doClear:

    @Override
    public void clear() {

        byte[] pattern = conversionService.convert(createCacheKey("*"), byte[].class);
        cacheWriter.clean(name, pattern);
    }

查看createCacheKey方法:

    /**
     * Customization hook for creating cache key before it gets serialized.
     *
     * @param key will never be {@literal null}.
     * @return never {@literal null}.
     */
    protected String createCacheKey(Object key) {

        // 將 object 類型的key轉(zhuǎn)化為string類型
        String convertedKey = convertKey(key);

        // 如果 redis 配置的是不自動為key添加前綴,則直接返回
        if (!cacheConfig.usePrefix()) {
            return convertedKey;
        }

        // 如果配置了使用前綴,則對key進(jìn)行前綴包裝
        return prefixCacheKey(convertedKey);
    }

那么,usePrefix是在哪里設(shè)置的呢?我們使用spring boot + redis的話一般都有一個配置類,用來配置redis的相關(guān)信息,如下:

@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
public class RedisConfig {

    @Bean
    @ConditionalOnMissingBean(name = "redisTemplate")
    public RedisTemplate<Object, Object> redisTemplate( RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();

        //使用fastjson序列化
        FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer(Object.class);
        // value值的序列化采用fastJsonRedisSerializer
        template.setValueSerializer(fastJsonRedisSerializer);
        template.setHashValueSerializer(fastJsonRedisSerializer);
        // key的序列化采用StringRedisSerializer
        template.setKeySerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());

        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    @Bean
    @ConditionalOnMissingBean(StringRedisTemplate.class)
    public StringRedisTemplate stringRedisTemplate(
            RedisConnectionFactory redisConnectionFactory) {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofMinutes(5))
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()))
                .disableCachingNullValues();
        RedisCacheManager.RedisCacheManagerBuilder builder =
                RedisCacheManager.RedisCacheManagerBuilder.fromConnectionFactory(redisConnectionFactory);
        return builder.transactionAware().cacheDefaults(config).build();
    }
}

在創(chuàng)建cacheManger這個bean的時候,可以看到這么一句代碼:

RedisCacheConfiguration.defaultCacheConfig()

defaultCacheConfig方法里面,則初始化并返回了一個RedisCacheConfiguration對象:

return new RedisCacheConfiguration(Duration.ZERO, true, true, CacheKeyPrefix.simple(),
                SerializationPair.fromSerializer(new StringRedisSerializer()),
                SerializationPair.fromSerializer(new JdkSerializationRedisSerializer()), conversionService);

這里的第三個構(gòu)造參數(shù)便是usePrefix,可見默認(rèn)值為true。此時生成的key規(guī)則為:cacheName::*,那接下來就是清除緩存的代碼:

cacheWriter.clean(name, pattern);

clean方法的redis實(shí)現(xiàn)版本中,可以看到clean最終也是使用keys獲取符合條件的keySet,之后通過del方法進(jìn)行刪除:

    /*
     * (non-Javadoc)
     * @see org.springframework.data.redis.cache.RedisCacheWriter#clean(java.lang.String, byte[])
     */
    @Override
    public void clean(String name, byte[] pattern) {

        Assert.notNull(name, "Name must not be null!");
        Assert.notNull(pattern, "Pattern must not be null!");

        execute(name, connection -> {

            boolean wasLocked = false;

            try {

                if (isLockingCacheWriter()) {
                    doLock(name, connection);
                    wasLocked = true;
                }

                byte[][] keys = Optional.ofNullable(connection.keys(pattern)).orElse(Collections.emptySet())
                        .toArray(new byte[0][]);

                if (keys.length > 0) {
                    connection.del(keys);
                }
            } finally {

                if (wasLocked && isLockingCacheWriter()) {
                    doUnlock(name, connection);
                }
            }

            return "OK";
        });
    }

疑問:眾所周知,redis是單線程的,使用keys可能會相當(dāng)耗時,按照以上的邏輯,是會對寫操作進(jìn)行加鎖,那么一旦keys耗時很久,也就意味著此時寫入緩存就會等待很久,從而導(dǎo)致接口層面上的超時,所以,這種實(shí)現(xiàn)合理嗎?

看完doClear方法,再來看看doEvict方法:

    /**
     * Execute {@link Cache#evict(Object)} on the specified {@link Cache} and
     * invoke the error handler if an exception occurs.
     */
    protected void doEvict(Cache cache, Object key) {
        try {
            cache.evict(key);
        }
        catch (RuntimeException ex) {
            getErrorHandler().handleCacheEvictError(ex, cache, key);
        }
    }

evict方法實(shí)現(xiàn)如下:

    @Override
    public void evict(Object key) {
        cacheWriter.remove(name, createAndConvertCacheKey(key));
    }

先根據(jù)key進(jìn)行創(chuàng)建和轉(zhuǎn)化得到包裝后的key,之后調(diào)用remove方法從name中將之刪除,remove最終也是使用了del方法:

    @Override
    public void remove(String name, byte[] key) {

        Assert.notNull(name, "Name must not be null!");
        Assert.notNull(key, "Key must not be null!");

        execute(name, connection -> connection.del(key));
    }

到這里processCacheEvicts就解析完畢了


  • findCachedItem
    @Nullable
    private Cache.ValueWrapper findCachedItem(Collection<CacheOperationContext> contexts) {
        Object result = CacheOperationExpressionEvaluator.NO_RESULT;
        for (CacheOperationContext context : contexts) {
            if (isConditionPassing(context, result)) {
                Object key = generateKey(context, result);
                Cache.ValueWrapper cached = findInCaches(context, key);
                if (cached != null) {
                    return cached;
                }
                else {
                    if (logger.isTraceEnabled()) {
                        logger.trace("No cache entry for key '" + key + "' in cache(s) " + context.getCacheNames());
                    }
                }
            }
        }
        return null;
    }

邏輯主要是遍歷多個@Cacheable,任何一個存在緩存數(shù)據(jù),直接返回對應(yīng)數(shù)據(jù),結(jié)束流程,否則返回null,findInCaches最后調(diào)用了get方法讀取緩存,該部分邏輯相對簡單,不廢話了


  • collectPutRequests:
    private void collectPutRequests(Collection<CacheOperationContext> contexts,
            @Nullable Object result, Collection<CachePutRequest> putRequests) {

        for (CacheOperationContext context : contexts) {
            if (isConditionPassing(context, result)) {
                Object key = generateKey(context, result);
                putRequests.add(new CachePutRequest(context, key));
            }
        }
    }

邏輯也比較簡單,遍歷contexts,若是滿足condition,生成key并包裝成CachePutRequest對象加入到putRequests中,注意此處的CachePutRequest

    private class CachePutRequest {

        private final CacheOperationContext context;

        private final Object key;

        public CachePutRequest(CacheOperationContext context, Object key) {
            this.context = context;
            this.key = key;
        }

        public void apply(@Nullable Object result) {
            if (this.context.canPutToCache(result)) {
                for (Cache cache : this.context.getCaches()) {
                    doPut(cache, this.key, result);
                }
            }
        }
    }

這里面實(shí)現(xiàn)了一個apply方法,該方法在非同步處理的主流程中最終會被調(diào)用,用于寫入緩存,寫入邏輯如下:

    @Override
    public void put(Object key, @Nullable Object value) {

        Object cacheValue = preProcessCacheValue(value);

        if (!isAllowNullValues() && cacheValue == null) {

            throw new IllegalArgumentException(String.format(
                    "Cache '%s' does not allow 'null' values. Avoid storing null via '@Cacheable(unless=\"#result == null\")' or configure RedisCache to allow 'null' via RedisCacheConfiguration.",
                    name));
        }

        cacheWriter.put(name, createAndConvertCacheKey(key), serializeCacheValue(cacheValue), cacheConfig.getTtl());
    }

代碼寫得很清晰了,不展開了


到此處,我們execute方法解析完畢!??!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容