Spring源码解析-Spring Cache

Spring版本

5.0.9.RELEASE

背景

最近正好在做缓存相关的项目,用到了Spring Cache搭配Redis实现,本着“知其然亦须知其所以然”的精神,研究了一下Spring Cache的源码,记录一下,以便深入理解。本文皆是基于自身理解撰写,能力有限,若有错误之处,不妨赐教。

1. Spring Cache提供的注解

1.1 Cacheable

被该注解修饰的方法在执行之前,会去缓存中查找是都存在对应key的缓存,若存在,则不执行方法,直接返回缓存结果,否则,执行方法并将结果缓存

1.1.1 属性

  • value:缓存名称,cacheNames的别名
  • cacheNames:缓存名称,value的别名,当RedisCacheConfiguration中的usePrefix配置为true时,作为key前缀
  • key:缓存的key,支持SpEL表达式,未定义默认的keyGenerator的情况下,使用方法的所有参数作为key,支持:
    1. #root.method:引用方法
    2. #root.target:引用目标对象
    3. #root.methodName:引用方法名
    4. #root.targetClass:来引用目标类
    5. #args[1]或者#a1或者#p1: 引用参数,也可以直接使用#参数名
  • keyGenerator:自定义的key生成器
  • cacheManager:自定义的缓存管理器
  • cacheResolver:自定义的缓存解析器
  • condition:缓存的前提条件,支持SpEL表达式,默认值为"",意味着方法执行结果永远会被缓存起来
  • unless:拒绝缓存的条件,支持SpEL表达式,默认为"",意味着不会拒绝缓存方法执行的结果,与condition不同的是,该表达式在方法执行结束之后生效,并且可以通过#result来引用执行后的结果
  • sync:是否开启同步,开启后仅允许被注解方法有且只有一个@Cacheable注解,并且不支持unless,并且不能同时拥有其他缓存注解,后文的源码解读中将体现sync=true的限制

1.2 CacheConfig

提供一个全局的配置,具体字段在@Cacheable中已经说明,此处不赘述

1.3 CacheEvict

字段与@Cacheable基本一致,多了以下几个字段:

1.3.1 属性

  • allEntries:默认false,代表按照key删除对应缓存,当指定为true,代表删除对应cacheNames下的全部缓存
  • beforeInvocation:是否在方法调用前触发删除逻辑,默认false,代表方法执行之后再删除缓存,若方法执行过程中抛出异常,不执行删除逻辑,设置为true,代表方法执行之前删除缓存,若方法执行过程中抛出异常,不影响删除逻辑

1.4 CachePut

@Cacheable不同的是,被该注解修饰的方法在执行之前,不会去缓存中检查是否存在对应key的缓存,而是直接执行方法并将结果缓存

1.5 Caching

@Cacheable@CachePut@CacheEvict的组合注解,方便我们对多个cache执行相应逻辑

1.6 EnableCaching

开启cache

1.6.1 属性

  • proxyTargetClass:仅当mode设置为Proxy时生效,为false代表jdk代理,为true代表cglib代理,设置为true同时会影响所有需要代理的且由spring管理的bean的代理模式,如被Transactional修饰
  • mode:Proxy:代理模式,AspectJ:切面模式,默认值Proxy
  • order:同个切入点有多个切面的时候的执行顺序

2. 源码解读

很明显,我们可以从EnableCaching作为源码阅读突破口,可以看到:

@Import(CachingConfigurationSelector.class)

EnableCaching中引入了CachingConfigurationSelector

@Import用于引入一个或多个Configuration,等效于在xml文件中的<import/>标签

结合EnableCachingmode默认为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俩个类,看名字应该是自动代理注册以及代理缓存配置,先看看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,启动项目,可以看到,该方法的入参importingClassMetadata传入的是我们的启动类:

Application

那么:

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

这一句很明显就是获取Application上面的注解:

EnableCaching

这里我们设置条件断点,只查看EnableCaching的执行流程,核心代码在于:

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

首先使用jdk代理进行注册,如果proxyTargetClass设置为true,则转化为cglib代理。
进入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对应的beanDefinition

  • 包含
    获取当前beanDefinition,如果和请求参数cls不是同一个bean,则根据优先级判断是否需要替换当前bean,那么优先级是如何定义的呢,点击任意findPriorityForClass方法:
private static int findPriorityForClass(Class<?> clazz) {
        return APC_PRIORITY_LIST.indexOf(clazz);
}

可以看到有一个集合APC_PRIORITY_LIST,查找初始化代码,可以看到其是在静态代码块中初始化了三个AutoProxyCreator,且通过index表明其优先级:

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

现在我们回过头来看看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方法中,我们可以看到设置了cacheOperationSourcecacheInterceptor,而cacheOperationSource则是AnnotationCacheOperationSource实例,查看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,方便我们后续对其处理(后面的源码中很多地方都会有CacheOperation的出场机会)
其中,localOnly代表如果被注解接口声明和实现同时存在缓存注解,那么,实现上的注解将会覆盖接口声明的注解:

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

疑问:为啥是覆盖而不是整合呢?待补充


再回过头来看看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主要的实现方法是execute,该方法的定义如下:

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

参数解析如下:

  • invoker:这里我们传入的是一个匿名方法,用于执行被注解的方法
  • target:被注解对象
  • method:被注解的方法
  • args:被注解方法的参数
    execute方法实现如下:
    @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)
        // 判断对象是否已经初始化,initialized默认为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));
                }
            }
        }
        // 缓存处理完毕之后,调用被注解方法执行逻辑
        return invoker.invoke();
    }

可以看到,此处调用了execute的重载方法,这里封装了一个缓存操作上下文对象作为参数,我们先看一下CacheOperationContexts这个内部类对象:

    private class CacheOperationContexts {
        // 缓存操作(@Cacheable、@CacheEvict等)和上下文环境的映射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标识为true,则认为是同步操作
            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)

该方法内部实现主要分成俩部分,一部分是对同步的处理,另一部分是非同步的处理

  • 同步:
    // CacheOperationContexts#determineSyncFlag解析的同步结果体现在这个条件判断中
    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 {
                // 执行缓存逻辑,同步交由缓存提供商实现,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
            // 不满足缓存条件,直接调用被注解方法
            return invokeOperation(invoker);
        }
    }

其中,cache#get方法在redis中的实现如下:

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

        // 从redis中读取缓存结果
        Cache.ValueWrapper result = get(key);

        // 缓存结果非空,直接以缓存结果作为返回值
        if (result != null) {
            return (T) result.get();
        }

        // 否则执行方法,获取执行结果,并缓存
        T value = valueFromLoader(key, valueLoader);
        put(key, value);
        return value;
    }

  • 不同步:
    // Process any early evictions
    // 处理beforeInvocation为true的@CacheEvict,该部分需要在方法调用之前处理
    processCacheEvicts(contexts.get(CacheEvictOperation.class), true,
    CacheOperationExpressionEvaluator.NO_RESULT);

    // Check if we have a cached item matching the conditions
    // 从缓存中获取缓存数据
    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<>();
    // 如果没有命中缓存,那么说明需要执行缓存写入逻辑,这里仅仅只是收集应该被写入缓存的@Cacheable数据到cachePutRequests中,真正的写入缓存操作在后续的apply中
    if (cacheHit == null) {
        collectPutRequests(contexts.get(CacheableOperation.class),
                CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);
    }

    // 应该被缓存的值
    Object cacheValue;
    // 方法返回值,与cacheValue的区别在于可能是一个Optional对象
    Object returnValue;

    // @CachePut注解表示无视缓存结果,强制执行方法并将结果缓存,所以这里必须判断是否有该注解
    if (cacheHit != null && !hasCachePut(contexts)) {
        // If there are no put requests, just use the cache hit
        // 命中缓存,且没有@CachePut注解的情况则直接返回缓存结果
        cacheValue = cacheHit.get();
        returnValue = wrapCacheValue(method, cacheValue);
    }
    else {
        // Invoke the method if we don't have a cache hit
        // 未命中缓存,则执行方法逻辑,获取执行结果和缓存结果
        returnValue = invokeOperation(invoker);
        cacheValue = unwrapReturnValue(returnValue);
    }

    // Collect any explicit @CachePuts
    // 收集应该被写入缓存的@CachePut数据到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
    // 执行后续的@CacheEvict逻辑
    processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);

    return returnValue;

以上我们讲解了缓存的处理主流程,接下来详细看看每个子节点的具体实现逻辑:

  • 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);
            }
        }
    }

核心点在于通过isConditionPassing方法判断是否符合删除前提条件,isConditionPassing中涉及到SpEL表达式的逻辑,不是本文的重点,此处不予展开。若满足前提条件,执行performCacheEvict方法来进行缓存删除逻辑:

    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);
                // 根据key删除对应缓存
                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转化为string类型
        String convertedKey = convertKey(key);

        // 如果 redis 配置的是不自动为key添加前缀,则直接返回
        if (!cacheConfig.usePrefix()) {
            return convertedKey;
        }

        // 如果配置了使用前缀,则对key进行前缀包装
        return prefixCacheKey(convertedKey);
    }

那么,usePrefix是在哪里设置的呢?我们使用spring boot + redis的话一般都有一个配置类,用来配置redis的相关信息,如下:

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

在创建cacheManger这个bean的时候,可以看到这么一句代码:

RedisCacheConfiguration.defaultCacheConfig()

defaultCacheConfig方法里面,则初始化并返回了一个RedisCacheConfiguration对象:

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

这里的第三个构造参数便是usePrefix,可见默认值为true。此时生成的key规则为:cacheName::*,那接下来就是清除缓存的代码:

cacheWriter.clean(name, pattern);

clean方法的redis实现版本中,可以看到clean最终也是使用keys获取符合条件的keySet,之后通过del方法进行删除:

    /*
     * (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可能会相当耗时,按照以上的逻辑,是会对写操作进行加锁,那么一旦keys耗时很久,也就意味着此时写入缓存就会等待很久,从而导致接口层面上的超时,所以,这种实现合理吗?

看完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方法实现如下:

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

先根据key进行创建和转化得到包装后的key,之后调用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,任何一个存在缓存数据,直接返回对应数据,结束流程,否则返回nullfindInCaches最后调用了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);
                }
            }
        }
    }

这里面实现了一个apply方法,该方法在非同步处理的主流程中最终会被调用,用于写入缓存,写入逻辑如下:

    @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方法解析完毕!!!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343