Spring Cache source code analysis

Spring Cache source code analysis

2018-05-05 10:55:20 Saturday

1. @EnableCaching

We enable Spring's caching functionality by using @EnableCaching in the configuration class.

The source code of @EnableCaching is as follows:

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

    boolean proxyTargetClass() default false;

    AdviceMode mode() default AdviceMode.PROXY;

    int order() default Ordered.LOWEST_PRECEDENCE;

}

You can see that there are three properties:

  1. mode: Specifies the mode of AOP. When the value is AdviceMode.PROXY, it means using Spring aop. When the value is AdviceMode.ASPECTJ, it means using AspectJ.
  2. proxyTargetClass: When the attribute value is false, it means using jdk proxy, and when it is true, it means using cglib proxy.

@EnableCaching introduces the CachingConfigurationSelector class through the @Import annotation. The main code of this class is as follows:

public class CachingConfigurationSelector extends AdviceModeImportSelector<EnableCaching> {

    //...

    @Override
    public String[] selectImports(AdviceMode adviceMode) {
        switch (adviceMode) {
            case PROXY:
                return getProxyImports();
            case ASPECTJ:
                return getAspectJImports();
            default:
                return null;
        }
    }

    private String[] getProxyImports() {
        List<String> result = new ArrayList<String>();
        result.add(AutoProxyRegistrar.class.getName());
        result.add(ProxyCachingConfiguration.class.getName());
        if (jsr107Present && jcacheImplPresent) {
            result.add(PROXY_JCACHE_CONFIGURATION_CLASS);
        }
        return result.toArray(new String[result.size()]);
    }

    //...
}

As can be seen from the source code, CachingConfigurationSelector injects AutoProxyRegistrar and ProxyCachingConfiguration into the IOC container.

2. AutoProxyRegistrar

We know that in order to use Spring aop, in addition to the proxy class (target) and the aspect (advisor:advice+pointcut), we also need to create proxy objects, and AutoProxyCreator can automatically create proxy objects for us. For specific reference: Automatic creation Proxy AutoProxyCreator

Part of the code of AutoProxyRegistrar is as follows:

public class AutoProxyRegistrar implements ImportBeanDefinitionRegistrar {


    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

        //...
        if (mode == AdviceMode.PROXY) {
            AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
            if ((Boolean) proxyTargetClass) {
                AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
                return;
            }
        }
        //...
    }

}

The method is called internally AopConfigUtils.registerAutoProxyCreatorIfNecessary(), and its role is to register an AutoProxyCreator in the IOC container.

Part of the code of AopConfigUtils is as follows:

public static BeanDefinition registerAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry) {
    return registerAutoProxyCreatorIfNecessary(registry, null);
}

public static BeanDefinition registerAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry, Object source) {
    return registerOrEscalateApcAsRequired(InfrastructureAdvisorAutoProxyCreator.class, registry, source);
}

As can be seen from the above code, the AutoProxyCreator that is finally injected into the IOC container is the InfrastructureAdvisorAutoProxyCreator. The particularity of InfrastructureAdvisorAutoProxyCreator is that it only automatically creates proxy objects for infrastructure-type Advisors. The source code is as follows:

public class InfrastructureAdvisorAutoProxyCreator extends AbstractAdvisorAutoProxyCreator {

    private ConfigurableListableBeanFactory beanFactory;


    @Override
    protected void initBeanFactory(ConfigurableListableBeanFactory beanFactory) {
        super.initBeanFactory(beanFactory);
        this.beanFactory = beanFactory;
    }

    @Override
    protected boolean isEligibleAdvisorBean(String beanName) {
        return (this.beanFactory.containsBeanDefinition(beanName) &&
                this.beanFactory.getBeanDefinition(beanName).getRole() == BeanDefinition.ROLE_INFRASTRUCTURE);
    }

}

The isEligibleAdvisorBean determines which Advisors will automatically create proxy objects for. As can be seen from the code, only the role is the satisfying condition of BeanDefinition.ROLE_INFRASTRUCTURE.

3. ProxyCachingConfiguration

ProxyCachingConfiguration is the key point. The source code is as follows:

@Configuration
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());
        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;
    }

}

ProxyCachingConfiguration is a configuration class that injects 3 beans into the IOC container. The most important is the BeanFactoryCacheOperationSourceAdvisor, which can be seen as an Advisor. Seeing this, it is clear, because there is already an AopPorxyCreator in the container, and now the Advisor (Advice+Pointcut) is injected into the IOC container, and the AOP that Spring Cache depends on can start to work.

3.1 AnnotationCacheOperationSource

AnnotationCacheOperationSource implements the CacheOperationSource interface, which has only one method:

public interface CacheOperationSource {

    Collection<CacheOperation> getCacheOperations(Method method, Class<?> targetClass);

}

CacheOperation is an abstraction of cache operations, and its implementation classes include CacheEvictOperation, CachePutOperation, and CacheableOperation.

The role of CacheOperationSource is to obtain all cache operations on the specified method. AnnotationCacheOperationSource is finally delegated to CacheAnnotationParser for processing:

protected Collection<CacheOperation> findCacheOperations(final Method method) {
    return determineCacheOperations(new CacheOperationProvider() {
        @Override
            public Collection<CacheOperation> getCacheOperations(CacheAnnotationParser parser) {
            return parser.parseCacheAnnotations(method);
        }
    });
}

The parseCacheAnnotations method of CacheAnnotationParser is as follows:

protected Collection<CacheOperation> parseCacheAnnotations(DefaultCacheConfig cachingConfig, AnnotatedElement ae) {
    Collection<CacheOperation> ops = null;

    Collection<Cacheable> cacheables = AnnotatedElementUtils.findAllMergedAnnotations(ae, Cacheable.class);
    if (!cacheables.isEmpty()) {
        ops = lazyInit(ops);
        for (Cacheable cacheable : cacheables) {
            ops.add(parseCacheableAnnotation(ae, cachingConfig, cacheable));
        }
    }
    Collection<CacheEvict> evicts = AnnotatedElementUtils.findAllMergedAnnotations(ae, CacheEvict.class);
    if (!evicts.isEmpty()) {
        ops = lazyInit(ops);
        for (CacheEvict evict : evicts) {
            ops.add(parseEvictAnnotation(ae, cachingConfig, evict));
        }
    }
    Collection<CachePut> puts = AnnotatedElementUtils.findAllMergedAnnotations(ae, CachePut.class);
    if (!puts.isEmpty()) {
        ops = lazyInit(ops);
        for (CachePut put : puts) {
            ops.add(parsePutAnnotation(ae, cachingConfig, put));
        }
    }
    Collection<Caching> cachings = 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;
}

3.2 BeanFactoryCacheOperationSourceAdvisor

ProxyCachingConfiguration configures Advisor using:

@Bean(name = CacheManagementConfigUtils.CACHE_ADVISOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    public BeanFactoryCacheOperationSourceAdvisor cacheAdvisor() {
    BeanFactoryCacheOperationSourceAdvisor advisor =
        new BeanFactoryCacheOperationSourceAdvisor();
    advisor.setCacheOperationSource(cacheOperationSource());
    advisor.setAdvice(cacheInterceptor());
    advisor.setOrder(this.enableCaching.<Integer>getNumber("order"));
    return advisor;
}

The cacheAdvisor() method uses @Role to specify the return value of BeanFactoryCacheOperationSourceAdvisor. The role attribute value is BeanDefinition.ROLE_INFRASTRUCTURE, which is why InfrastructureAdvisorAutoProxyCreator can automatically create a proxy for BeanFactoryCacheOperationSourceAdvisor.

BeanFactoryCacheOperationSourceAdvisor injects CacheInterceptor as an enhancement method.

The source code of BeanFactoryCacheOperationSourceAdvisor is as follows:

public class BeanFactoryCacheOperationSourceAdvisor extends AbstractBeanFactoryPointcutAdvisor {

    private CacheOperationSource cacheOperationSource;

    private final CacheOperationSourcePointcut pointcut = new CacheOperationSourcePointcut() {
        @Override
        protected CacheOperationSource getCacheOperationSource() {
            return cacheOperationSource;
        }
    };

    public void setCacheOperationSource(CacheOperationSource cacheOperationSource) {
        this.cacheOperationSource = cacheOperationSource;
    }

    public void setClassFilter(ClassFilter classFilter) {
        this.pointcut.setClassFilter(classFilter);
    }

    @Override
    public Pointcut getPointcut() {
        return this.pointcut;
    }

}

Advisor consists of Advice and Pointcut. Advice is injected in ProxyCachingConfiguration and Pointcut uses CacheOperationSourcePointcut.

The source code of CacheOperationSourcePointcut is as follows:

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

    protected abstract CacheOperationSource getCacheOperationSource();

}

As can be seen from the source code, only methods annotated with @Cacheable, @CachePut or @CacheEvict will be matched.

3.3 CacheInterceptor

Configure the CacheInterceptor in the ProxyCachingConfiguration using:

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

Its class is defined as follows:

public class CacheInterceptor extends CacheAspectSupport implements MethodInterceptor, Serializable {
    @Override
    public Object invoke(final MethodInvocation invocation) throws Throwable {
        Method method = invocation.getMethod();

        CacheOperationInvoker aopAllianceInvoker = new CacheOperationInvoker() {
            @Override
            public Object invoke() {
                try {
                    return invocation.proceed();
                }
                catch (Throwable ex) {
                    throw new ThrowableWrapper(ex);
                }
            }
        };

        try {
            return execute(aopAllianceInvoker, invocation.getThis(), method, invocation.getArguments());
        }
        catch (CacheOperationInvoker.ThrowableWrapper th) {
            throw th.getOriginal();
        }
    }

}

It can be seen that CacheInterceptor is a surround enhancement. Its main method is implemented in CacheAspectSupport.

The implementation in CacheAspectSupport is as follows, the first is the execute method:

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);
        Collection<CacheOperation> operations = getCacheOperationSource().getCacheOperations(method, targetClass);
        if (!CollectionUtils.isEmpty(operations)) {
            return execute(invoker, method, new CacheOperationContexts(operations, method, args, target, targetClass));
        }
    }

    return invoker.invoke();
}

This method encapsulates the target method, the parameters of the target method, the target class, the cache operation used on the target method, etc. into CacheOperationContexts and passes it down. A MultiValueMap is stored in CacheOperationContexts, and the source code is as follows:

private class CacheOperationContexts {

    private final MultiValueMap<Class<? extends CacheOperation>, CacheOperationContext> contexts =
        new LinkedMultiValueMap<Class<? extends CacheOperation>, CacheOperationContext>();

    private final boolean sync;

    public CacheOperationContexts(Collection<? extends CacheOperation> operations, Method method,
                                  Object[] args, Object target, Class<?> targetClass) {

        for (CacheOperation operation : operations) {
            this.contexts.add(operation.getClass(), getOperationContext(operation, method, args, target, targetClass));
        }
        this.sync = determineSyncFlag(method);
    }

    public Collection<CacheOperationContext> get(Class<? extends CacheOperation> operationClass) {
        Collection<CacheOperationContext> result = this.contexts.get(operationClass);
        return (result != null ? result : Collections.<CacheOperationContext>emptyList());
    }
    //...
}

When calling the add method of LinkedMultiValueMap, the value of the same key will be stored in a List. Each CacheOperationContext will encapsulate a CacheOperation. When the get method is called, the corresponding CacheOperationContext list will be returned.

invoke(final MethodInvocation invocation)The method also calls down the execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts)method. The source code is as follows:

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)) {
            Object key = generateKey(context, CacheOperationExpressionEvaluator.NO_RESULT);
            Cache cache = context.getCaches().iterator().next();
            try {
                return cache.get(key, new Callable<Object>() {
                    @Override
                        public Object call() throws Exception {
                        return 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
    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<CachePutRequest>();
    if (cacheHit == null) {
        collectPutRequests(contexts.get(CacheableOperation.class),
                           CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);
    }

    Object cacheValue;
    Object returnValue;

    if (cacheHit != null && cachePutRequests.isEmpty() && !hasCachePut(contexts)) {
        // If there are no put requests, just use the cache hit
        cacheValue = cacheHit.get();
        if (method.getReturnType() == javaUtilOptionalClass &&
            (cacheValue == null || cacheValue.getClass() != javaUtilOptionalClass)) {
            returnValue = OptionalUnwrapper.wrap(cacheValue);
        }
        else {
            returnValue = cacheValue;
        }
    }
    else {
        // Invoke the method if we don't have a cache hit
        returnValue = invokeOperation(invoker);
        if (returnValue != null && returnValue.getClass() == javaUtilOptionalClass) {
            cacheValue = OptionalUnwrapper.unwrap(returnValue);
        }
        else {
            cacheValue = 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;
}

1. if (contexts.isSynchronized()){} code block

Because only the @Cacheable annotation has the sync attribute, this part of the code is used to handle @Cacheable related operations.

// 1. 执行@Cacheable注解对应的操作:只有@Cacheable注解有sync属性,当sync为true时,contexts.isSynchronized()返回true,执行以下方法
if (contexts.isSynchronized()) {
    CacheOperationContext context = contexts.get(CacheableOperation.class).iterator().next(); //一个方法上只允许标注一个@Cacheable
    if (isConditionPassing(context, CacheOperationExpressionEvaluator.NO_RESULT)) { //如果@Cacheable的condition属性为空字符串,或者满足condition条件则执行以下方法
        Object key = generateKey(context, CacheOperationExpressionEvaluator.NO_RESULT);
        Cache cache = context.getCaches().iterator().next();
        try {
            return cache.get(key, new Callable<Object>() {  //执行底层Cache的get方法,当Cache中得不到的会先执行目标方法,然后将结果保存到Cache中
                @Override
                    public Object call() throws Exception {
                    return 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);
    }
}

The get method of the underlying cache is used in the code, taking ConcurrentMapCache as an example: The get method of ConcurrentMapCache is as follows:

public <T> T get(Object key, Callable<T> valueLoader) {
    if (this.store.containsKey(key)) {  //1. 尝试从缓存中获得key对应的value,存在则返回
        return (T) get(key).get();
    }
    else {
        synchronized (this.store) {
            if (this.store.containsKey(key)) { //再次尝试获得缓存纪录
                return (T) get(key).get();
            }
            T value;
            try {
                value = valueLoader.call(); //2. 如果获取不到,则执行目标方法
            }
            catch (Exception ex) {
                throw new ValueRetrievalException(key, valueLoader, ex);
            }
            put(key, value); //3. 将目标方法的返回值放入缓存中
            return value;//4. 返回
        }
    }
}

2. processCacheEvicts method

The @CacheEvict annotation has a beforeInvocation attribute. When the attribute value is true, it means that the cache is cleared before the target method is executed. When false, it means that the cache will not be cleared until the target method is executed.

So the processCacheEvicts method is called twice in the execute method:

// Process any early evictions
processCacheEvicts(contexts.get(CacheEvictOperation.class), true,CacheOperationExpressionEvaluator.NO_RESULT);

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

The previous call will only clear the cache when beforeInvocation is true, and the second call will only clear the cache when beforeInvocation is false. The processCacheEvicts method is as follows:

private void processCacheEvicts(Collection<CacheOperationContext> contexts, boolean beforeInvocation, Object result) {
    for (CacheOperationContext context : contexts) {
        CacheEvictOperation operation = (CacheEvictOperation) context.metadata.operation;
        if (beforeInvocation == operation.isBeforeInvocation() && isConditionPassing(context, result)) {
            performCacheEvict(context, operation, result);
        }
    }
}

private void performCacheEvict(CacheOperationContext context, CacheEvictOperation operation, Object result) {
    Object key = null;
    for (Cache cache : context.getCaches()) {
        if (operation.isCacheWide()) {
            logInvalidating(context, operation, null);
            doClear(cache);
        }
        else {
            if (key == null) {
                key = context.generateKey(result);
            }
            logInvalidating(context, operation, key);
            doEvict(cache, key);
        }
    }
}

The cacheWide property of CacheEvictOperation corresponds to allEntries annotated with @CacheEvict, and the org.springframework.cache.annotation.SpringCacheAnnotationParser#parseEvictAnnotation method is as follows:

CacheEvictOperation parseEvictAnnotation(AnnotatedElement ae, DefaultCacheConfig defaultConfig, CacheEvict cacheEvict) {
    CacheEvictOperation.Builder builder = new CacheEvictOperation.Builder();

    builder.setName(ae.toString());
    builder.setCacheNames(cacheEvict.cacheNames());
    builder.setCondition(cacheEvict.condition());
    builder.setKey(cacheEvict.key());
    builder.setKeyGenerator(cacheEvict.keyGenerator());
    builder.setCacheManager(cacheEvict.cacheManager());
    builder.setCacheResolver(cacheEvict.cacheResolver());
    builder.setCacheWide(cacheEvict.allEntries());
    builder.setBeforeInvocation(cacheEvict.beforeInvocation());

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

    return op;
}

When operation.isCacheWide() is true, the doClear method will be executed to clear all contents in the cache. If it is false, execute the doEvict method to clear the specified record.

3. Analysis of @Cacheable

//1. 尝试从缓存中获得key对应的值
Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class));

// 2. 如果没有获取到就将CacheOperationContext封装到CachePutRequest中,并保存到cachePutRequests集合内
List<CachePutRequest> cachePutRequests = new LinkedList<CachePutRequest>();
if (cacheHit == null) {
    collectPutRequests(contexts.get(CacheableOperation.class),
                       CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);
}

Object cacheValue;
Object returnValue;

//2. 如果获得到了对应的值就将结果保存到returnValue属性值中
if (cacheHit != null && cachePutRequests.isEmpty() && !hasCachePut(contexts)) {
    // If there are no put requests, just use the cache hit
    cacheValue = cacheHit.get();
    if (method.getReturnType() == javaUtilOptionalClass &&
        (cacheValue == null || cacheValue.getClass() != javaUtilOptionalClass)) {
        returnValue = OptionalUnwrapper.wrap(cacheValue);
    }
    else {
        returnValue = cacheValue;
    }
}
else {
    //3. 如果获取不到就执行目标方法
    returnValue = invokeOperation(invoker);
    if (returnValue != null && returnValue.getClass() == javaUtilOptionalClass) {
        cacheValue = OptionalUnwrapper.unwrap(returnValue);
    }
    else {
        cacheValue = returnValue;
    }
}

If the current method is not marked with the @Cacheable annotation or the sync attribute of @Cacheable is not true, the above code will be executed.

For the @Cacheable annotation, first execute the findCachedItem method to try to obtain the value corresponding to the key from the cache. If it is obtained, save the result to the returnValue property; if it cannot be obtained, run the target method and save the result to the returnValue property , and encapsulate the corresponding CacheOperationContext as CachePutRequest, and save it to the cachePutRequests collection (call the collectPutRequests method).

For @CachePut, because the cachehit property value is always null, the target method will be run and the result will be saved in the returnValue variable:

returnValue = invokeOperation(invoker);

4. collectPutRequests

The @CachePut annotation is parsed at the end of the execute method:

// Collect any explicit @CachePuts
collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);

The collectPutRequests method is mainly used:

private void collectPutRequests(Collection<CacheOperationContext> contexts,Object result, Collection<CachePutRequest> putRequests) {

    for (CacheOperationContext context : contexts) {
        if (isConditionPassing(context, result)) {
            Object key = generateKey(context, result);
            putRequests.add(new CachePutRequest(context, key));
        }
    }
}

It can be seen that the function of collectPutRequests is to encapsulate each CacheOperationContext representing @CachePut as a CachePutRequest, and then save it into the cachePutRequests collection.

Now the cachePutRequests collection has @CachePut (corresponding ContextOperationContext) in addition to @Cacheable (corresponding ContextOperationContext) that cannot get values ​​in the cache.

5. Write cache

The final step of execute is to run the CachePutRequest in the collection one by one:

for (CachePutRequest cachePutRequest : cachePutRequests) {
    cachePutRequest.apply(cacheValue);
}

cacheValue can simply be thought of as an alias for returnValue.

Let's take a look at the source code of 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(Object result) {
        if (this.context.canPutToCache(result)) {
            for (Cache cache : this.context.getCaches()) {
                doPut(cache, this.key, result);
            }
        }
    }
}

The canPutToCache method of CacheOperationContext is as follows:

protected boolean canPutToCache(Object value) {
    String unless = "";
    if (this.metadata.operation instanceof CacheableOperation) {
        unless = ((CacheableOperation) this.metadata.operation).getUnless();
    }
    else if (this.metadata.operation instanceof CachePutOperation) {
        unless = ((CachePutOperation) this.metadata.operation).getUnless();
    }
    if (StringUtils.hasText(unless)) {
        EvaluationContext evaluationContext = createEvaluationContext(value);
        return !evaluator.unless(unless, this.methodCacheKey, evaluationContext);
    }
    return true;
}

Only the execution results of methods marked with @Cacheable and @CachePut can be stored in the cache. If these two annotations do not specify the unless attribute, then the execution result will be put into the cache, otherwise only when the result meets the unless attribute is allowed to be put into the cache.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325732463&siteId=291194637