Home  >  Article  >  Java  >  Understanding Spring Boot cache source code

Understanding Spring Boot cache source code

不言
不言forward
2018-11-16 15:56:572210browse

The content of this article is about the understanding of Spring Boot cache source code. It has certain reference value. Friends in need can refer to it. I hope it will be helpful to you.

I want to add an application cache to the project. I originally thought about how to integrate ehcache and springboot, and be ready to configure this configuration and that one. In the end, I only need to do three things:

pom dependency

Write an ehcache configuration file

Add the annotation @EnableCaching to the boot application.
That’s it, isn’t it magical?

pom dependency

<dependency>
            <groupid>net.sf.ehcache</groupid>
            <artifactid>ehcache</artifactid>
            <version>2.10.5</version>
</dependency>

Configuration file

<?xml  version="1.0" encoding="UTF-8"?>
<ehcache>
    <!-- 设定缓存的默认数据过期策略 -->
    <defaultcache></defaultcache>
</ehcache>

Add EnableCaching annotation to the application

@SpringBootApplication
@EnableCaching
public class EhCacheApplication {

    public static void main(String[] args) {
        SpringApplication.run(EhCacheApplication.class, args);
    }
}

Then you can use cache annotations in the code, like this.

@CachePut(value = "fish-ehcache", key = "#person.id")
    public Person save(Person person) {
        System.out.println("为id、key为:" + person.getId() + "数据做了缓存");
        return person;
    }

    @CacheEvict(value = "fish-ehcache")
    public void remove(Long id) {
        System.out.println("删除了id、key为" + id + "的数据缓存");
    }


    @Cacheable(value = "fish-ehcache", key = "#person.id")
    public Person findOne(Person person) {
        findCount.incrementAndGet();
        System.out.println("为id、key为:" + person.getId() + "数据做了缓存");
        return person;
    }

It’s very convenient, right? Next, let’s dig a little deeper and see how spring does it. It is mainly divided into two parts, one is what is done when starting, the second is what is done when running, and the third is the adaptation to third-party cache components

What is done when starting,

This starts with the @EnableCaching tag. When using the caching function, the annotation @EnableCaching needs to be added to the Springboot Application startup class. This tag introduces the

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

Introduction of the CachingConfigurationSelector class , this class enables the configuration of the cache function. This class adds two classes: AutoProxyRegistrar.java and ProxyCachingConfiguration.java.

  • AutoProxyRegistrar: Implements the ImportBeanDefinitionRegistrar interface. I don’t understand here, I need to continue learning.

  • ProxyCachingConfiguration: is a configuration class that generates three beans: BeanFactoryCacheOperationSourceAdvisor, CacheOperationSource, and CacheInterceptor.

CacheOperationSource encapsulates the parsing work of cache method signature annotations and forms a collection of CacheOperations. CacheInterceptor uses this collection filter to perform cache processing. The class that parses cache annotations is SpringCacheAnnotationParser, and its main methods are as follows

/**
由CacheOperationSourcePointcut作为注解切面,会解析
SpringCacheAnnotationParser.java
扫描方法签名,解析被缓存注解修饰的方法,将生成一个CacheOperation的子类并将其保存到一个数组中去
**/
protected Collection<cacheoperation> parseCacheAnnotations(SpringCacheAnnotationParser.DefaultCacheConfig cachingConfig, AnnotatedElement ae) {
        Collection<cacheoperation> ops = null;
        //找@cacheable注解方法
        Collection<cacheable> cacheables = AnnotatedElementUtils.getAllMergedAnnotations(ae, Cacheable.class);
        if (!cacheables.isEmpty()) {
            ops = this.lazyInit(ops);
            Iterator var5 = cacheables.iterator();

            while(var5.hasNext()) {
                Cacheable cacheable = (Cacheable)var5.next();
                ops.add(this.parseCacheableAnnotation(ae, cachingConfig, cacheable));
            }
        }
        //找@cacheEvict注解的方法
        Collection<cacheevict> evicts = AnnotatedElementUtils.getAllMergedAnnotations(ae, CacheEvict.class);
        if (!evicts.isEmpty()) {
            ops = this.lazyInit(ops);
            Iterator var12 = evicts.iterator();

            while(var12.hasNext()) {
                CacheEvict evict = (CacheEvict)var12.next();
                ops.add(this.parseEvictAnnotation(ae, cachingConfig, evict));
            }
        }
        //找@cachePut注解的方法
        Collection<cacheput> puts = AnnotatedElementUtils.getAllMergedAnnotations(ae, CachePut.class);
        if (!puts.isEmpty()) {
            ops = this.lazyInit(ops);
            Iterator var14 = puts.iterator();

            while(var14.hasNext()) {
                CachePut put = (CachePut)var14.next();
                ops.add(this.parsePutAnnotation(ae, cachingConfig, put));
            }
        }
        Collection<caching> cachings = AnnotatedElementUtils.getAllMergedAnnotations(ae, Caching.class);
        if (!cachings.isEmpty()) {
            ops = this.lazyInit(ops);
            Iterator var16 = cachings.iterator();

            while(var16.hasNext()) {
                Caching caching = (Caching)var16.next();
                Collection<cacheoperation> cachingOps = this.parseCachingAnnotation(ae, cachingConfig, caching);
                if (cachingOps != null) {
                    ops.addAll(cachingOps);
                }
            }
        }
        return ops;
}</cacheoperation></caching></cacheput></cacheevict></cacheable></cacheoperation></cacheoperation>

The methods corresponding to the four annotations of Cachable, Caching, CachePut, and CachEevict are saved in the Collection collection.

What is done when executing the method

When executing, the CacheInterceptor class is mainly used.

public class CacheInterceptor extends CacheAspectSupport implements MethodInterceptor, Serializable {
    public CacheInterceptor() {
    }

    public Object invoke(final MethodInvocation invocation) throws Throwable {
        Method method = invocation.getMethod();
        CacheOperationInvoker aopAllianceInvoker = new CacheOperationInvoker() {
            public Object invoke() {
                try {
                    return invocation.proceed();
                } catch (Throwable var2) {
                    throw new ThrowableWrapper(var2);
                }
            }
        };

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

This interceptor inherits the CacheAspectSupport class and MethodInterceptor interface. Among them, CacheAspectSupport encapsulates the main logic. For example, the following paragraph.

/**
CacheAspectSupport.java
执行@CachaEvict @CachePut @Cacheable的主要逻辑代码
**/

private Object execute(final CacheOperationInvoker invoker, Method method, CacheAspectSupport.CacheOperationContexts contexts) {
        if (contexts.isSynchronized()) {
            CacheAspectSupport.CacheOperationContext context = (CacheAspectSupport.CacheOperationContext)contexts.get(CacheableOperation.class).iterator().next();
            if (this.isConditionPassing(context, CacheOperationExpressionEvaluator.NO_RESULT)) {
                Object key = this.generateKey(context, CacheOperationExpressionEvaluator.NO_RESULT);
                Cache cache = (Cache)context.getCaches().iterator().next();

                try {
                    return this.wrapCacheValue(method, cache.get(key, new Callable<object>() {
                        public Object call() throws Exception {
                            return CacheAspectSupport.this.unwrapReturnValue(CacheAspectSupport.this.invokeOperation(invoker));
                        }
                    }));
                } catch (ValueRetrievalException var10) {
                    throw (ThrowableWrapper)var10.getCause();
                }
            } else {
                return this.invokeOperation(invoker);
            }
        } else {
            /**
            执行@CacheEvict的逻辑,这里是当beforeInvocation为true时清缓存
            **/
            this.processCacheEvicts(contexts.get(CacheEvictOperation.class), true, CacheOperationExpressionEvaluator.NO_RESULT);
            //获取命中的缓存对象
            ValueWrapper cacheHit = this.findCachedItem(contexts.get(CacheableOperation.class));
            List<cacheaspectsupport.cacheputrequest> cachePutRequests = new LinkedList();
            if (cacheHit == null) {
                //如果没有命中,则生成一个put的请求
                this.collectPutRequests(contexts.get(CacheableOperation.class), CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);
            }


            Object cacheValue;
            Object returnValue;
            /**
                如果没有获得缓存对象,则调用业务方法获得返回对象,hasCachePut会检查exclude的情况
            **/
            if (cacheHit != null && cachePutRequests.isEmpty() && !this.hasCachePut(contexts)) {
                cacheValue = cacheHit.get();
                returnValue = this.wrapCacheValue(method, cacheValue);
            } else {
                
                returnValue = this.invokeOperation(invoker);
                cacheValue = this.unwrapReturnValue(returnValue);
            }

            this.collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);
            Iterator var8 = cachePutRequests.iterator();

            while(var8.hasNext()) {
                CacheAspectSupport.CachePutRequest cachePutRequest = (CacheAspectSupport.CachePutRequest)var8.next();
                /**
                执行cachePut请求,将返回对象放到缓存中
                **/
                cachePutRequest.apply(cacheValue);
            }
            /**
            执行@CacheEvict的逻辑,这里是当beforeInvocation为false时清缓存
            **/
            this.processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);
            return returnValue;
        }
    }</cacheaspectsupport.cacheputrequest></object>

The above code snippets are relatively core and are the contents of the cache. As for the source code of aop, I will not go into details here. It should be studied in a separate article. The main classes and interfaces are in the spring context, in the org.springframework.cache package.

Adaptation to third-party cache components

Through the above analysis, we know the ins and outs of the spring cache function. What needs to be analyzed below is why only the maven statement is needed. With just a few dependencies, spring boot can automatically adapt.

In the above execution method, we see cachePutRequest.apply(cacheValue), where the cache will be operated, and CachePutRequest is CacheAspectSupport inner class.

private class CachePutRequest {
        private final CacheAspectSupport.CacheOperationContext context;
        private final Object key;
        public CachePutRequest(CacheAspectSupport.CacheOperationContext context, Object key) {
            this.context = context;
            this.key = key;
        }
        public void apply(Object result) {
            if (this.context.canPutToCache(result)) {
                //从context中获取cache实例,然后执行放入缓存的操作
                Iterator var2 = this.context.getCaches().iterator();
                while(var2.hasNext()) {
                    Cache cache = (Cache)var2.next();
                    CacheAspectSupport.this.doPut(cache, this.key, result);
                }
            }
        }
    }

Cache is a standard interface, among which EhCacheCache is the implementation class of EhCache. This is the connection between SpringBoot and Ehcache, so when is the cache list in the context generated? The answer is the getCaches method of CacheAspectSupport

protected Collection extends Cache> getCaches(CacheOperationInvocationContext<cacheoperation> context, CacheResolver cacheResolver) {
        Collection extends Cache> caches = cacheResolver.resolveCaches(context);
        if (caches.isEmpty()) {
            throw new IllegalStateException("No cache could be resolved for '" + context.getOperation() + "' using resolver '" + cacheResolver + "'. At least one cache should be provided per cache operation.");
        } else {
            return caches;
        }
    }</cacheoperation>

and obtaining the cache is executed every time a cache operation is performed. You can take a look at the call stack

Understanding Spring Boot cache source code

#It seems a bit off topic, bring it back... In the spring-boot-autoconfigure package, there are all auto-assembly related classes. There is an EhcacheCacheConfiguration class here, as follows

@Configuration
@ConditionalOnClass({Cache.class, EhCacheCacheManager.class})
@ConditionalOnMissingBean({CacheManager.class})
@Conditional({CacheCondition.class, EhCacheCacheConfiguration.ConfigAvailableCondition.class})
class EhCacheCacheConfiguration {
 ......
 static class ConfigAvailableCondition extends ResourceCondition {
        ConfigAvailableCondition() {
            super("EhCache", "spring.cache.ehcache", "config", new String[]{"classpath:/ehcache.xml"});
        }
    }    
}

This will directly determine whether there is an ehcache.xml file in the class path

The above is the detailed content of Understanding Spring Boot cache source code. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:segmentfault.com. If there is any infringement, please contact admin@php.cn delete