spring源码------@EnableCaching,@Cacheable,@CacheEvict,@CachePut的实现原理

 spring在3.1版本中增加了缓存的支持。但是不进行Cache的缓存策略的维护,只是提供了一个Wrapper,提供一套对外一致的API。
 spring主要提供了6个注解来对缓存进行操作。

注解 作用
@EnableCaching 是否开启缓存功能
@CacheConfig 缓存的配置
@Cacheable 声明一个方法的调用结果可以被缓存,先从缓存中读取,如果没有再调用方法获取数据,然后把数据添加到缓存中
@CacheEvict 调用方法时会从缓存中移除相应的数据:
@CachePut 调用方法时会自动把相应的数据放入缓存:
@Caching @Cacheable@CacheEvict@CachePut三个注解集合

1. @EnableCaching如何开启缓存的功能

1.1 从@Import注解派生出来的@EnableCaching

 看@EnableCaching注解上面有一个@Import注解,并且指定的类是CachingConfigurationSelector类。对于@Import注解熟悉的就会知道他的作用。这里不对这个注解进行讲解,可以参考前面的一篇博文6.1Spring的AOP的解析——AOP的自定义组件

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(CachingConfigurationSelector.class)
public @interface EnableCaching {

    /**
     * 指示是否创建基于子类(CGLIB)的代理,而不是标准的基于Java接口的代理。
     */
    boolean proxyTargetClass() default false;

    /**
     * 代理模式
     */
    AdviceMode mode() default AdviceMode.PROXY;

    
    int order() default Ordered.LOWEST_PRECEDENCE;

}
1.2 CachingConfigurationSelectorAdviceModeImportSelector注入bean到容器

 从上面分析,这里直接进入到CachingConfigurationSelector类中。

public class CachingConfigurationSelector extends AdviceModeImportSelector<EnableCaching> {
    ......
    
    @Override
    public String[] selectImports(AdviceMode adviceMode) {
        switch (adviceMode) {
            //默认的是PROXY类型的,返回需要支持这个类型需要注入的bean
            case PROXY:
                return getProxyImports();
            case ASPECTJ:
                return getAspectJImports();
            default:
                return null;
        }
    }
    
    private String[] getProxyImports() {
        List<String> result = new ArrayList<>(3);
        //启动aop的注解
        result.add(AutoProxyRegistrar.class.getName());
        //代理配置类,这里会注入,
        // BeanFactoryCacheOperationSourceAdvisor类
        // CacheInterceptor类
        result.add(ProxyCachingConfiguration.class.getName());
        if (jsr107Present && jcacheImplPresent) {
            //注入BeanFactoryJCacheOperationSourceAdvisor
            //JCacheInterceptor类
            result.add(PROXY_JCACHE_CONFIGURATION_CLASS);
        }
        return StringUtils.toStringArray(result);
    }
    ......
}

 这里发现没有应该实现的ImportSelector接口的selectImports方法。这个方法的实现在其父类AdviceModeImportSelector中。进入到AdviceModeImportSelectorselectImports方法中。

    public final String[] selectImports(AnnotationMetadata importingClassMetadata) {
        //获取AdviceModeImportSelector中的注解泛型的Class对象
        Class<?> annType = GenericTypeResolver.resolveTypeArgument(getClass(), AdviceModeImportSelector.class);
        Assert.state(annType != null, "Unresolvable type argument for AdviceModeImportSelector");
        //从传入的importingClassMetadata对象中获取对应的Class类型的注解的内部属性
        AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(importingClassMetadata, annType);
        if (attributes == null) {
            throw new IllegalArgumentException(String.format(
                    "@%s is not present on importing class '%s' as expected",
                    annType.getSimpleName(), importingClassMetadata.getClassName()));
        }
        //获取属性中的代理类型属性“mode”的值
        AdviceMode adviceMode = attributes.getEnum(getAdviceModeAttributeName());
        //根据代理类型获取需要注入的bean
        String[] imports = selectImports(adviceMode);
        if (imports == null) {
            throw new IllegalArgumentException("Unknown AdviceMode: " + adviceMode);
        }
        return imports;
    }

 结合上面的两段代码进行说明顺序:

  1. 首先进入到AdviceModeImportSelectorselectImports方法中。这个类会获取指定的泛型注解中的属性,然后调用由子类来实现的selectImports方法,选择需要注入容器的bean。
  2. CachingConfigurationSelector中实现了selectImports。这个方法中会根据@EnableCaching注解中的mode属性来选择注入不同的bean。这里默认情况下mode属性是PROXY类型的,所以进入到getProxyImports方法中。
  3. getProxyImports方法中。会向容器中注入两个bean,AutoProxyRegistrarProxyCachingConfiguration。另外一个ProxyJCacheConfiguration类型的bean在是当前类的加载器是javax.cache.Cacheorg.springframework.cache.jcache.config.ProxyJCacheConfiguration父类的时候会被注册。

 这里说明一下步骤3注册的前两个bean 的作用:

bean 作用
AutoProxyRegistrar 缓存的值的保存跟获取,会用到aop,这个类是启动aop。是@EnableAspectJAutoProxy注解实现类
ProxyCachingConfiguration 缓存的配置信息类
1.3 ProxyCachingConfiguration缓存注解拦截相关的配置

ProxyCachingConfiguration会在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() {
        //缓存操作的Advisor,间接继承了AbstractPointcutAdvisor,能返回指定的切点
        BeanFactoryCacheOperationSourceAdvisor advisor = new BeanFactoryCacheOperationSourceAdvisor();
        //设置缓存操作源,也就是切点源,AOP的pointcut属性
        advisor.setCacheOperationSource(cacheOperationSource());
        //设置advice,拦截器
        advisor.setAdvice(cacheInterceptor());
        if (this.enableCaching != null) {
            advisor.setOrder(this.enableCaching.<Integer>getNumber("order"));
        }
        return advisor;
    }

    @Bean
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    public CacheOperationSource cacheOperationSource() {
        //返回注解操作缓存的操作源,AnnotationCacheOperationSource的父类AbstractFallbackCacheOperationSource里面包含了对注解相关的解析
        return new AnnotationCacheOperationSource();
    }

    @Bean
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    public CacheInterceptor cacheInterceptor() {
        //创建对应的拦截器,aop时候拦截的时候用到
        CacheInterceptor interceptor = new CacheInterceptor();
        //设置对应的配置
        interceptor.configure(this.errorHandler, this.keyGenerator, this.cacheResolver, this.cacheManager);
        //为缓存的切面这只操作源
        interceptor.setCacheOperationSource(cacheOperationSource());
        return interceptor;
    }
}

 可以看到这里面主要配置的东西就是跟AOP相关的信息,主要就是切点源以及拦截器。关于AOP相关的可以看看前面的文章Spring的AOP的解析——AOP的自定义组件,对于AOP的讲解还是比较详细的。
 上面的配置中除了表面看到的AOP相关的配置,这里还需要注意一点就是,在CacheInterceptor在创建之后调用了一个configure方法,而这个方法里面传入了四个参数,分别是errorHandlerkeyGeneratorcacheResolver以及cacheManager。这里说明一下四个参数的作用,然后一一进行分析。

参数对应的对象 作用
errorHandler 在对应的缓存操作出错的时候进行处理,可以制定错误类型
keyGenerator 缓存键生成器。用于根据给定的方法*(用作上下文)及其参数创建键
cacheResolver 确定缓存信息的缓存对象类型
cacheManager 缓存管理器,用于获取缓存的
    public void configure(
            @Nullable Supplier<CacheErrorHandler> errorHandler, @Nullable Supplier<KeyGenerator> keyGenerator,
            @Nullable Supplier<CacheResolver> cacheResolver, @Nullable Supplier<CacheManager> cacheManager) {
        //设置errorHandler,如果没有指定errorHandler则使用SimpleCacheErrorHandler作为默认的错误处理器
        this.errorHandler = new SingletonSupplier<>(errorHandler, SimpleCacheErrorHandler::new);
        //设置keyGenerator,如果没有指定keyGenerator则使用默认的SimpleKeyGenerator作为默认的缓存key生成器
        this.keyGenerator = new SingletonSupplier<>(keyGenerator, SimpleKeyGenerator::new);
        //设置cacheResolver,如果没有指定cacheResolver则根据cacheManager来生成SimpleCacheResolver
        this.cacheResolver = new SingletonSupplier<>(cacheResolver,
                () -> SimpleCacheResolver.of(SupplierUtils.resolve(cacheManager)));
    }
1.3.1 CacheErrorHandler缓存操作错误的处理器

CacheErrorHandler是用处理在操作缓存时抛出的指定RuntimeException类型异常的一个接口类,其中定义了四个四个方法分别对应对缓存的四种操作的时候可能出错的操作。

方法名 操作
handleCacheGetError 处理在获取缓存操作时抛出的指定异常
handleCachePutError 处理在更新缓存操作时抛出的指定异常
handleCacheEvictError 处理在清除指定key缓存操作时抛出的指定异常
handleCacheClearError 处理在清楚指定Cache缓存操作时抛出的指定异常

 从上面的configure方法片段中可以看出这个类默认实现是SimpleCacheErrorHandler,在这个实现类中,对于错误的处理逻辑很简单就是简单的抛出我们指定的异常。

public class SimpleCacheErrorHandler implements CacheErrorHandler {

    @Override
    public void handleCacheGetError(RuntimeException exception, Cache cache, Object key) {
        throw exception;
    }

    @Override
    public void handleCachePutError(RuntimeException exception, Cache cache, Object key, @Nullable Object value) {
        throw exception;
    }

    @Override
    public void handleCacheEvictError(RuntimeException exception, Cache cache, Object key) {
        throw exception;
    }

    @Override
    public void handleCacheClearError(RuntimeException exception, Cache cache) {
        throw exception;
    }
}
1.3.2 KeyGenerator缓存的key生成器

 跟类名的意思一样,KeyGenerator的作用就是生成缓存的key用的。这个接口只有一个方法generate方法用来生成一个缓存的key。在上面的configure方法中设置了在没有指定的情况下使用的是SimpleKeyGenerator类。看看这个类里面的方法。

public class SimpleKeyGenerator implements KeyGenerator {

    @Override
    public Object generate(Object target, Method method, Object... params) {
        //生成key对象
        return generateKey(params);
    }

    /**
     * Generate a key based on the specified parameters.
     */
    public static Object generateKey(Object... params) {
        //如果生成key的时候的参数为空的,那就直接返回一个空的SimpleKey对象
        if (params.length == 0) {
            return SimpleKey.EMPTY;
        }
        //如果参数只有一个并且不是数组类型的,则直接返回原对象
        if (params.length == 1) {
            Object param = params[0];
            if (param != null && !param.getClass().isArray()) {
                return param;
            }
        }
        //如果参数是多个,则将这些参数保存到一个Object类型的数组中,然后保存到SimpleKey对象中,并返回SimpleKey对象
        return new SimpleKey(params);
    }

}

 可以看到当key参数是多个或者为空的时候,都是用的SimpleKey对象作为key的。如果只有一个参数,那么就用这个参数作为key。

1.3.3 CacheResolver确定拦截方法调用的缓存

 这个接口中也是只有一个方法resolveCaches,这个方法的作用就是从缓存操作的上下文获取对应的Cache对象,在后续的对缓存的保存,删除方法拦截的时候获取操作对象用。

@FunctionalInterface
public interface CacheResolver {
    Collection<? extends Cache> resolveCaches(CacheOperationInvocationContext<?> context);

}

 这个类的子类AbstractCacheResolver实现了InitializingBean接口的afterPropertiesSet方法。在这个方法里面会检查CacheManager是不是null。说明CacheManager是必须的,这个类后面会进行讲解。
 在初始化配置的时候,如果没有指定CacheResolver的实现,那么就会使用的默认的SimpleCacheResolver类。SimpleCacheResolver类只实现了一个获取根据上下文获取缓存名的方法getCacheNames。而CacheResolver接口中resolveCachesAbstractCacheResolver实现的

    public Collection<? extends Cache> resolveCaches(CacheOperationInvocationContext<?> context) {
        //调用需要子类实现的getCacheNames获取缓存的名称
        Collection<String> cacheNames = getCacheNames(context);
        if (cacheNames == null) {
            return Collections.emptyList();
        }
        Collection<Cache> result = new ArrayList<>(cacheNames.size());
        //依次从CacheManager中根据缓存的cacheName获取Cache对象,并保存到要返回的集合中
        for (String cacheName : cacheNames) {
            Cache cache = getCacheManager().getCache(cacheName);
            if (cache == null) {
                throw new IllegalArgumentException("Cannot find cache named '" +
                        cacheName + "' for " + context.getOperation());
            }
            result.add(cache);
        }
        return result;
    }
1.3.4 CacheManager缓存对象的管理器

CacheManager用来管理Cache对象的接口类,主要提供了两个方法分别获取缓存的名跟缓存对象。

public interface CacheManager {
    //根据缓存的名称获取指定的Cache对象
    @Nullable
    Cache getCache(String name);

    Collection<String> getCacheNames();

}

 这个对象是必须的有实例对象的,在AbstractCacheResolver中会检查这个对象是不是为null。配置的时候CacheManager默认的是SimpleCacheManager。spring中还增加了对ehcache以及JSR-107的Jcache的支持,以及一个可以配置多个CacheManagerCompositeCacheManager,只不过需要配置。这里列举一下这些持续需要配置的类

缓存类型 对应的配置类
EhCache EhCacheCacheManager
Jcache JCacheCacheManager
复合 CompositeCacheManager
1.4 CacheInterceptor实现对缓存的设置获取以及删除

CacheInterceptor实现类MethodInterceptor可以拦截前面在配置的时候设定的切点,这里再次把前面的配置展示说明一下,因为这里比较难以理解。

public class ProxyCachingConfiguration extends AbstractCachingConfiguration {
    @Bean(name = CacheManagementConfigUtils.CACHE_ADVISOR_BEAN_NAME)
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    public BeanFactoryCacheOperationSourceAdvisor cacheAdvisor() {
        //缓存操作的Advisor,间接继承了AbstractPointcutAdvisor,能返回指定的切点
        BeanFactoryCacheOperationSourceAdvisor advisor = new BeanFactoryCacheOperationSourceAdvisor();
        //设置缓存操作源,也就是切点源,AOP的pointcut属性
        advisor.setCacheOperationSource(cacheOperationSource());
        //设置advice,拦截器
        advisor.setAdvice(cacheInterceptor());
        ......
    }
}
1.4.1 setCacheOperationSource方法

 这里创建的BeanFactoryCacheOperationSourceAdvisor对象间接实现类PointcutAdvisor接口的getPointcut方法,这个方法在AOP确定advisor能否在代理目标类上适用的时候会调用具体在AopUtils里面,而setCacheOperationSource方法间接设置了pointcut属性。这里截取部分代码,具体的还是可以去看前面的aop相关的文章。6.1Spring的AOP的解析——AOP的自定义组件

    public static boolean canApply(Advisor advisor, Class<?> targetClass, boolean hasIntroductions) {
        //如果是IntroductionAdvisor类型的,则直接获取到ClassFilter之后然后进行匹配返回结果
        if (advisor instanceof IntroductionAdvisor) {
            return ((IntroductionAdvisor) advisor).getClassFilter().matches(targetClass);
        }
        //如果是PointcutAdvisor类型的
        else if (advisor instanceof PointcutAdvisor) {
            PointcutAdvisor pca = (PointcutAdvisor) advisor;
            //如果是PointcutAdvisor类型的需要获取到对应的Pointcut,然后分别对Class跟method进行校验看是否合适
            return canApply(pca.getPointcut(), targetClass, hasIntroductions);
        }
        else {
            // It doesn't have a pointcut so we assume it applies.
            return true;
        }
    }
1.4.1 setAdvice方法

 后面还有一个setAdvice方法。这个方法就是将CacheInterceptor加入到spring的候选的Advice对象中。这里设置了之后,后面就能对缓存的操作方法进行拦截。

1.4.2 pointcut对应的CacheOperationSourcePointcut

 前面说到了AopUtil中会获取设置的Pointcut后会先获取其中的ClassFilter并调用matches方法检查当前需要被代理的类是否合适PointcutAdvisor,然后获取其中的MethodMatchermatches方法检查对应的方法是否合适PointcutAdvisor
 而配置中CacheOperationSourcePointcut就是对应的Pointcut,这里看一下这个类。

abstract class CacheOperationSourcePointcut extends StaticMethodMatcherPointcut implements Serializable {
    protected CacheOperationSourcePointcut() {
        //设置Pointcut用的ClassFilter
        setClassFilter(new CacheOperationSourceClassFilter());
    }
    
    @Override
    public boolean matches(Method method, Class<?> targetClass) {
        //获取设置的CacheOperationSource,在配置的时候设置的是AnnotationCacheOperationSource类
        CacheOperationSource cas = getCacheOperationSource();
        //检查是否存在对应的目标类的方法上面是否存在,缓存操作
        return (cas != null && !CollectionUtils.isEmpty(cas.getCacheOperations(method, targetClass)));
    }
    
    ......

        private class CacheOperationSourceClassFilter implements ClassFilter {

        @Override
        public boolean matches(Class<?> clazz) {
            if (CacheManager.class.isAssignableFrom(clazz)) {
                return false;
            }
            //获取设置的CacheOperationSource,在配置的时候设置的是AnnotationCacheOperationSource类
            CacheOperationSource cas = getCacheOperationSource();
            //检查目标类是不是候选类,这个isCandidateClass最后调用的是,SpringCacheAnnotationParser的isCandidateClass方法
            return (cas == null || cas.isCandidateClass(clazz));
        }
    }
}

 可以看到对应的匹配的方法都在这个类里面。这些都决定是否进行拦截。
&esmp;现在正式进入到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();
        }
    }

}

 经过前面的分析已经知道,能够进入到这里的方法都是贴有那些缓存操作@Cacheable@CacheEvict等注解的方法。在这里主要就是调用那些方法,获取到方法的结果然后,进入到父类CacheAspectSupport中进一步处理。

1.5 CacheAspectSupport操作缓存

 现在直接进入到被CacheInterceptor调用的execute方法

    protected Object execute(CacheOperationInvoker invoker, Object target, Method method, Object[] args) {
        //检查切面是否已经准备好了
        if (this.initialized) {
            //获取目标类
            Class<?> targetClass = getTargetClass(target);
            //获取cacheOperationSource默认的是AnnotationCacheOperationSource,父类是AbstractFallbackCacheOperationSource
            CacheOperationSource cacheOperationSource = getCacheOperationSource();
            if (cacheOperationSource != null) {
                //获取目标类的目标方法上的缓存相关的注解
                Collection<CacheOperation> operations = cacheOperationSource.getCacheOperations(method, targetClass);
                if (!CollectionUtils.isEmpty(operations)) {
                    /**
                     * invoker为贴有注解方法的调用结果
                     * method 为贴有注解的方法
                     */
                    return execute(invoker, method,
                            //根据注解上的相关的信息生成CacheOperationContexts,指定 CacheResolver跟 KeyGenerator
                            new CacheOperationContexts(operations, method, args, target, targetClass));
                }
            }
        }
        return invoker.invoke();
    }

 上面的这个方法的作用主要就是获取当前贴有注解的方法上的所有的缓存操作的注解包括@Cacheable@CacheEvict@CachePut@Caching这些注解,生成一个缓存操作的上下文,在生成的上下文中会包含生成的Cache对象,这里不对生成上下文的过程进行讲解。生成上下文信息之后,然后调用另外的一个execute方法进行处理。下面的execute才是创建Cache对象的位置。

    private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {
        // Special handling of synchronized invocation
        //是否是同步的方法
        if (contexts.isSynchronized()) {
            CacheOperationContext context = contexts.get(CacheableOperation.class).iterator().next();
            if (isConditionPassing(context, CacheOperationExpressionEvaluator.NO_RESULT)) {
                //生成key
                Object key = generateKey(context, CacheOperationExpressionEvaluator.NO_RESULT);
                //从缓存操作上下文中获取cache
                Cache cache = context.getCaches().iterator().next();
                try {
                    //调用invoke方法,然后把调用的结果保存起来
                    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);
            }
        }


        // Process any early evictions
        //执行@CacheEvict注解的处理逻辑类CacheEvictOperation,如果CacheEvictOperation=true
        processCacheEvicts(contexts.get(CacheEvictOperation.class), true,
                CacheOperationExpressionEvaluator.NO_RESULT);

        // Check if we have a cached item matching the conditions
        //执行@Cacheable注解的处理逻辑类CacheableOperation,获取对应的缓存数据
        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注解收集不到chache(condition 通过,且key对应的数据不在缓存)
        if (cacheHit == null) {
            //如果缓存不存在,但是存在@CachePut操作,则将缓存的值放到cachePutRequests中
            collectPutRequests(contexts.get(CacheableOperation.class),
                    CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);
        }

        Object cacheValue;
        Object returnValue;
        //如果CachePutRequest不是空则说明存在缓存,并且没有CachePut操作,则直接从缓存获取。然后将缓存值包装未返回值
        if (cacheHit != null && !hasCachePut(contexts)) {
            // If there are no put requests, just use the cache hit
            cacheValue = cacheHit.get();
            returnValue = wrapCacheValue(method, cacheValue);
        }
        else {
            //todo 如果没有缓存或者说存在CachePut操作,这时候调用方法获取方法的返回值,然后将返回值包装为缓存值
            returnValue = invokeOperation(invoker);
            cacheValue = unwrapReturnValue(returnValue);
        }

        // Collect any explicit @CachePuts
        //将缓存的值放到cachePutRequests中
        collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);

        // Process any collected put requests, either from @CachePut or a @Cacheable miss
        //将cachePutRequests中的值一次保存到缓存中
        for (CachePutRequest cachePutRequest : cachePutRequests) {
            cachePutRequest.apply(cacheValue);
        }

        // Process any late evictions
        //执行@CacheEvict,方法调用之后
        processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);

        return returnValue;
    }

 上面的这些操作的步骤进行讲解一下:

  1. 首先执行@CacheEvict(如果beforeInvocation=true且condition 通过),如果allEntries=true,则清空所有
  2. 接着收集@Cacheable(如果condition 通过,且key对应的数据不在缓存),放入cachePutRequests(也就是说如果cachePutRequests为空,则数据在缓存中)
  3. 如果cachePutRequests为空且没有@CachePut操作,那么将查找@Cacheable的缓存,否则result=缓存数据(也就是说只要当没有cache put请求时才会查找缓存)
  4. 如果没有找到缓存,那么调用实际的API,把结果放入result
  5. 如果有@CachePut操作(如果condition 通过),那么放入cachePutRequests
  6. 执行cachePutRequests,将数据写入缓存(unless为空或者unless解析结果为false);
  7. 执行@CacheEvict(如果beforeInvocation=false 且 condition 通过),如果allEntries=true,则清空所有
  8. 返回结果值

 这里需要注意2/3/4步:
 如果有@CachePut操作,即使有@Cacheable也不会从缓存中读取;问题很明显,如果要混合多个注解使用,不能组合使用@CachePut@Cacheable;官方说应该避免这样使用(解释是如果带条件的注解相互排除的场景);不过个人感觉还是不要考虑这个好,让用户来决定如何使用,否则一会介绍的场景不能满足。

 到这里@EnableCaching@Cacheable@CacheEvict@CachePut的实现原理就完毕了。

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

推荐阅读更多精彩内容