美文网首页
Spring源码解析-Spring Cache

Spring源码解析-Spring Cache

作者: 秋水畏寒 | 来源:发表于2020-03-29 17:25 被阅读0次

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

    相关文章

      网友评论

          本文标题:Spring源码解析-Spring Cache

          本文链接:https://www.haomeiwen.com/subject/wciayhtx.html