美文网首页
【每天学点Spring】Spring Cache源码分析

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

作者: 伊丽莎白2015 | 来源:发表于2022-12-24 17:15 被阅读0次

关于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

【参考】

相关文章

网友评论

      本文标题:【每天学点Spring】Spring Cache源码分析

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