【每天学点Spring】Spring Cache源码分析

关于Spring Cache的介绍,参考:https://www.jianshu.com/p/51389fbaf411,本文主要讲关于注解@Cacheable, @CachePut, @CacheEvict背后是怎样实现的。

1. 相关类介绍

  • 1.1 Cache, CacheManager: 这两个接口是Spring Cache的核心接口。

    • Cache接口主要定义了cache的操作,如get, put等。
    • CacheManager接口定义的是cache的集合,为什么会有多个Cache对象主要是每个cache可能会有自己的过期时间、Cache的元素需要被限制等原因。
  • 1.2 CacheInterceptor, CacheAspectSupport, AbstractCacheInvoker:标记Cache注解的方法被调用时的AOP相关的类。

    • CacheInterceptor继承了AOP的MethodInterceptor,其中方法invoke(invocation)方法相当于@Aspect中的Around,可以在目标方法之前和之后写一些额外的逻辑,比如从cache中查询数据,或是写数据至cache等。
    • CacheAspectSupport:真正提供cache操作的类,被上述的CacheInterceptor继承。
    • AbstractCacheInvoker:抽象类,被上述的CacheAspectSupport继续,提供读cache、写cache等操作,如方法:doGet(cache, key) , doPut(cache, key, result)等。
  • 1.3 CacheOperation, AnnotationCacheOperationSource, SpringCacheAnnotationParser:主要是为annotation做解析的。

    • CacheOperation是个抽象类,定义了cache的name\key\condition等,继承它的主要也是上述三个注解的operation,分别是:CacheableOperation, CachePutOperation, CacheEvictOperation
    • AnnotationCacheOperationSource类主要是读取Spring Cache的三个annotation(@Cacheable, @CachePut, @CacheEvict),转成CacheOperation
    • SpringCacheAnnotationParser类则负责具体的annotation parse逻辑。

2. Annotation解析

2.1 SpringCacheAnnotationParser

在1.3中介绍,SpringCacheAnnotationParser类负责具体的annotation parse逻辑。

public class SpringCacheAnnotationParser implements CacheAnnotationParser, Serializable {
    @Override
    @Nullable
    public Collection<CacheOperation> parseCacheAnnotations(Method method) {
        DefaultCacheConfig defaultConfig = new DefaultCacheConfig(method.getDeclaringClass());
        return parseCacheAnnotations(defaultConfig, method);
    }

    @Nullable
    private Collection<CacheOperation> parseCacheAnnotations(DefaultCacheConfig cachingConfig, AnnotatedElement ae) {
        Collection<CacheOperation> ops = parseCacheAnnotations(cachingConfig, ae, false);
        if (ops != null && ops.size() > 1) {
            // More than one operation found -> local declarations override interface-declared ones...
            Collection<CacheOperation> localOps = parseCacheAnnotations(cachingConfig, ae, true);
            if (localOps != null) {
                return localOps;
            }
        }
        return ops;
    }

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

        Collection<? extends Annotation> anns = (localOnly ?
                AnnotatedElementUtils.getAllMergedAnnotations(ae, CACHE_OPERATION_ANNOTATIONS) :
                AnnotatedElementUtils.findAllMergedAnnotations(ae, CACHE_OPERATION_ANNOTATIONS));
        if (anns.isEmpty()) {
            return null;
        }

        final Collection<CacheOperation> ops = new ArrayList<>(1);
        anns.stream().filter(ann -> ann instanceof Cacheable).forEach(
                ann -> ops.add(parseCacheableAnnotation(ae, cachingConfig, (Cacheable) ann)));
        anns.stream().filter(ann -> ann instanceof CacheEvict).forEach(
                ann -> ops.add(parseEvictAnnotation(ae, cachingConfig, (CacheEvict) ann)));
        anns.stream().filter(ann -> ann instanceof CachePut).forEach(
                ann -> ops.add(parsePutAnnotation(ae, cachingConfig, (CachePut) ann)));
        anns.stream().filter(ann -> ann instanceof Caching).forEach(
                ann -> parseCachingAnnotation(ae, cachingConfig, (Caching) ann, ops));
        return ops;
    }

    // 具体的parse逻辑:
    private CacheableOperation parseCacheableAnnotation(
            AnnotatedElement ae, DefaultCacheConfig defaultConfig, Cacheable cacheable) {

        CacheableOperation.Builder builder = new CacheableOperation.Builder();

        builder.setName(ae.toString());
        builder.setCacheNames(cacheable.cacheNames());
        builder.setCondition(cacheable.condition());
        builder.setUnless(cacheable.unless());
        builder.setKey(cacheable.key());
        builder.setKeyGenerator(cacheable.keyGenerator());
        builder.setCacheManager(cacheable.cacheManager());
        builder.setCacheResolver(cacheable.cacheResolver());
        builder.setSync(cacheable.sync());

        defaultConfig.applyDefault(builder);
        CacheableOperation op = builder.build();
        validateCacheOperation(ae, op);

        return op;
    }
}

比如我CourseService有个方法getById(int)上有@Cacheable注解:

@Cacheable(cacheNames = "courses")
public Course getById(int id) {...}

Debug上述parseCacheAnnotations()方法的结果,可以看到CourseService上的注解,最终会被转成CacheOperation对象,这个对象在#1.3有介绍过,主要是存放了注解的name, key, condition等信息:

image.png

【小结】SpringCacheAnnotationParser类图:

image.png

2.2 AnnotationCacheOperationSource

那么是谁在调用上述#2.1的parseCacheAnnotations()方法?

AnnotationCacheOperationSource在构造方法中new了上述的SpringCacheAnnotationParser,并且在findCacheOperations(method)方法中调用了#2.1的parse方法:

public class AnnotationCacheOperationSource extends AbstractFallbackCacheOperationSource implements Serializable {

    public AnnotationCacheOperationSource(boolean publicMethodsOnly) {
        this.publicMethodsOnly = publicMethodsOnly;
        this.annotationParsers = Collections.singleton(new SpringCacheAnnotationParser());
    }

    @Override
    @Nullable
    protected Collection<CacheOperation> findCacheOperations(Method method) {
        return determineCacheOperations(parser -> parser.parseCacheAnnotations(method));
    }
}

那么,findCacheOperations(method)是谁在调用呢?
AnnotationCacheOperationSource继续了抽象类AbstractFallbackCacheOperationSource,在抽象类中:

public abstract class AbstractFallbackCacheOperationSource implements CacheOperationSource {
    @Override
    @Nullable
    public Collection<CacheOperation> getCacheOperations(Method method, @Nullable Class<?> targetClass) {
        if (method.getDeclaringClass() == Object.class) {
            return null;
        }

        Object cacheKey = getCacheKey(method, targetClass);
        Collection<CacheOperation> cached = this.attributeCache.get(cacheKey);

        if (cached != null) {
            return (cached != NULL_CACHING_ATTRIBUTE ? cached : null);
        }
        else {
            Collection<CacheOperation> cacheOps = computeCacheOperations(method, targetClass);
            if (cacheOps != null) {
                if (logger.isTraceEnabled()) {
                    logger.trace("Adding cacheable method '" + method.getName() + "' with attribute: " + cacheOps);
                }
                this.attributeCache.put(cacheKey, cacheOps);
            }
            else {
                this.attributeCache.put(cacheKey, NULL_CACHING_ATTRIBUTE);
            }
            return cacheOps;
        }
    }

    @Nullable
    private Collection<CacheOperation> computeCacheOperations(Method method, @Nullable Class<?> targetClass) {
        // Don't allow non-public methods, as configured.
        if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
            return null;
        }

        // The method may be on an interface, but we need attributes from the target class.
        // If the target class is null, the method will be unchanged.
        Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);

        // First try is the method in the target class.
        Collection<CacheOperation> opDef = findCacheOperations(specificMethod);
        if (opDef != null) {
            return opDef;
        }

        // Second try is the caching operation on the target class.
        opDef = findCacheOperations(specificMethod.getDeclaringClass());
        if (opDef != null && ClassUtils.isUserLevelMethod(method)) {
            return opDef;
        }

        if (specificMethod != method) {
            // Fallback is to look at the original method.
            opDef = findCacheOperations(method);
            if (opDef != null) {
                return opDef;
            }
            // Last fallback is the class of the original method.
            opDef = findCacheOperations(method.getDeclaringClass());
            if (opDef != null && ClassUtils.isUserLevelMethod(method)) {
                return opDef;
            }
        }

        return null;
    }

    @Nullable
    protected abstract Collection<CacheOperation> findCacheOperations(Class<?> clazz);

    @Nullable
    protected abstract Collection<CacheOperation> findCacheOperations(Method method);
}

抽象类中:方法getCacheOperations(method, targetClass)
调用了 --> computeCacheOperations(method, targetClass),调用了抽象方法 --> findCacheOperations(method)

而方法getCacheOperations(method, targetClass)则在第3章介绍的类中被调用。

【小结】打星的方法即是annotation parse中最终会被外部调用的方法:
image.png

3. 目标方法被执行时的逻辑

本章节围绕CacheInterceptor相关类展开。

@Cacheable的定义是,在执行目标方法前,先查一遍缓存,如果缓存里有,那么就返回结果,如果没有,那么执行目标方法,并将结果写入缓存。

不难想象,用切面实现比较容易,即目标方法前做一些逻辑(查缓存,返回结果),目标方法后做一些逻辑(将结果写入缓存)。

创建CacheInterceptor

由类ProxyCachingConfiguration负责创建CacheInterceptor,该类位于spring-context包中,这个类本身是个Configuration,最终由@EnableCaching负责激活。

关于@EnableCache相关,具体参考:http://it.cha138.com/nginx/show-291168.html

ProxyCachingConfiguration具体源码,具体的解释在源码下面:

public class ProxyCachingConfiguration extends AbstractCachingConfiguration {

    @Bean
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    public CacheOperationSource cacheOperationSource() {
        return new AnnotationCacheOperationSource();
    }
    
    @Bean
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    public CacheInterceptor cacheInterceptor(CacheOperationSource cacheOperationSource) {
        CacheInterceptor interceptor = new CacheInterceptor();
        interceptor.configure(this.errorHandler, this.keyGenerator, this.cacheResolver, this.cacheManager);
        interceptor.setCacheOperationSource(cacheOperationSource);
        return interceptor;
    }

    @Bean(name = CacheManagementConfigUtils.CACHE_ADVISOR_BEAN_NAME)
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    public BeanFactoryCacheOperationSourceAdvisor cacheAdvisor(
            CacheOperationSource cacheOperationSource, CacheInterceptor cacheInterceptor) {

        BeanFactoryCacheOperationSourceAdvisor advisor = new BeanFactoryCacheOperationSourceAdvisor();
        advisor.setCacheOperationSource(cacheOperationSource);
        advisor.setAdvice(cacheInterceptor);
        if (this.enableCaching != null) {
            advisor.setOrder(this.enableCaching.<Integer>getNumber("order"));
        }
        return advisor;
    }
}

上述定义的ProxyCachingConfiguration,主要做了3件事:

  • a. 创建了我们上述第#2章的CacheOperationSource接口实现。
  • b. 创建了CacheInterceptor,并将a的值set到该类中。CacheInterceptor实现了MethodInterceptor接口,定义了切面方法的逻辑。
  • c. 定义Advisor,并定义切点(PointCut),将和上述b绑定在一起。这里定义的切点类为CacheOperationSourcePointcut,其中重要的方法(即每次匹配要用到的逻辑):matches()这里用到了第2章的annotation parse方法
abstract class CacheOperationSourcePointcut extends StaticMethodMatcherPointcut implements Serializable {
    @Override
    public boolean matches(Method method, Class<?> targetClass) {
        CacheOperationSource cas = getCacheOperationSource();
        return (cas != null && !CollectionUtils.isEmpty(cas.getCacheOperations(method, targetClass)));
    }
}

【小结】左边是第2章的类图,右边是本章的类图:
image.png
3.2 详解CacheInterceptorinvoke(invocation)方法

CacheInterceptor类源码:

  • 首先它实现了aop中的接口MethodInterceptor
  • 其次,继承了CacheAspectSupport类,该类提供的execute()方法即为具体的逻辑。
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);
            }
        };

        Object target = invocation.getThis();
        Assert.state(target != null, "Target must not be null");
        try {
            return execute(aopAllianceInvoker, target, method, invocation.getArguments());
        }
        catch (CacheOperationInvoker.ThrowableWrapper th) {
            throw th.getOriginal();
        }
    }
}

具体看CacheAspectSupport的execute()方法:

  • 先拿到targetClasss
  • 再拿到CacheOperationSource,这个类hold了注解@Cacheable, @CachePut, @CacheEvict的定义,即:Collection<CacheOperation> operations。
  • 调用第二个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)
        if (this.initialized) {
            Class<?> targetClass = getTargetClass(target);
            CacheOperationSource cacheOperationSource = getCacheOperationSource();
            if (cacheOperationSource != null) {
                Collection<CacheOperation> operations = cacheOperationSource.getCacheOperations(method, targetClass);
                if (!CollectionUtils.isEmpty(operations)) {
                    return execute(invoker, method,
                            new CacheOperationContexts(operations, method, args, target, targetClass));
                }
            }
        }
        return invoker.invoke();
    }

第二个execute()方法:

  • Synchronized case略过,看常规的case。
  • 当注解为@Cacheable时,查询cacheHit,不为空表示有缓存存在。如果为空,需要放入cache待写的list中(即:cachePutRequests)。
  • 然后就是进行判断:
    • 如果cacheHit不为空并且不是@CachePut操作,那么从缓存里拿到cacheValue。
    • 如果cacheHit为空,则执行真正的目标方法。
  • 如果为@CachePut操作,那么也需要放入cache待写的list中(即:cachePutRequests)。
  • 读取cachePutRequests,执行cache写操作。
  • @CacheEvict注解进行清理cache操作。
    @Nullable
    private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {
        // Special handling of synchronized invocation
        if (contexts.isSynchronized()) {
            // 略
        }

        // Process any early evictions
        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 ArrayList<>();
        if (cacheHit == null) {
            collectPutRequests(contexts.get(CacheableOperation.class),
                    CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);
        }

        Object cacheValue;
        Object returnValue;

        if (cacheHit != null && !hasCachePut(contexts)) {
            // If there are no put requests, just use the cache hit
            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
        collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);

        // Process any collected put requests, either from @CachePut or a @Cacheable miss
        for (CachePutRequest cachePutRequest : cachePutRequests) {
            cachePutRequest.apply(cacheValue);
        }

        // Process any late evictions
        processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);

        return returnValue;
    }

上述CacheAspectSupport类,execute()方法中的从缓存里拿数据,用的方法是:findCachedItem(contexts),具体源码:
可以看到findCachedItem(contexts)调用了--> findInCaches(context, key) --> 调用了doGet(cache, key),而doGet方法位于CacheAspectSupport的父类AbstractCacheInvoker中,该抽象类提供基础的doGet,doPut,doEvict方法。

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

    @Nullable
    private Cache.ValueWrapper findInCaches(CacheOperationContext context, Object key) {
        for (Cache cache : context.getCaches()) {
            Cache.ValueWrapper wrapper = doGet(cache, key);
            if (wrapper != null) {
                if (logger.isTraceEnabled()) {
                    logger.trace("Cache entry for key '" + key + "' found in cache '" + cache.getName() + "'");
                }
                return wrapper;
            }
        }
        return null;
    }

【小结】
image.png

【参考】

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

推荐阅读更多精彩内容