美文网首页
【每天学点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