SpringBoot缓存原理

Posted 帅东

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringBoot缓存原理相关的知识,希望对你有一定的参考价值。

三个注解

@Cacheable可以标记在一个方法上,也可以标记在一个类上。当标记在一个方法上时表示该方法是支持缓存的,当标记在一个类上时则表示该类所有的方法都是支持缓存的。对于一个支持缓存的方法,Spring会在其被调用后将其返回值缓存起来,以保证下次利用同样的参数来执行该方法时可以直接从缓存中获取结果,而不需要再次执行该方法。Spring在缓存方法的返回值时是以键值对进行缓存的,值就是方法的返回结果,至于键的话,Spring又支持两种策略,默认策略和自定义策略。需要注意的是当一个支持缓存的方法在对象内部被调用时是不会触发缓存功能的。

@CachePut也可以声明一个方法支持缓存功能。与@Cacheable不同的是使用@CachePut标注的方法在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中。

@CacheEvict是用来标注在需要清除缓存元素的方法或类上的。当标记在一个类上时表示其中所有的方法的执行都会触发缓存的清除操作。用beforeInvocation可以改变触发清除操作的时间,当我们指定该属性值为true时,Spring会在调用该方法之前清除缓存中的指定元素。

配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:cache="http://www.springframework.org/schema/cache"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/cache
           http://www.springframework.org/schema/cache/spring-cache.xsd">

    <cache:annotation-driven/>

    <bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
        <property name="caches">
            <set>
                <bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean">
                    <property name="name" value="userCache"/>
                </bean>
            </set>
        </property>
    </bean>
</beans>

缓存底层

可以看出Cacheable用的是SimpleCacheManager(缓存管理),里面放了一个缓存容器caches,spring默认提供了一个缓存工厂ConcurrentMapCacheFactoryBean
里面实际使用的缓存容器是:ConcurrentMapCache

public class ConcurrentMapCache extends AbstractValueAdaptingCache 

	private final String name;

	private final ConcurrentMap<Object, Object> store;

ConcurrentMapCache就是简单封装了一层ConcurrentMap,并没有提供额外优秀的功能(定时删除、容量限制…)

切面原理

路线:mvc -> spring-aop -> cglib -> spring-cache
spring-cache之前都是通用知识,不在本文描述范围内,从spring-cache开始
核心代码:org.springframework.cache.interceptor.CacheAspectSupport#execute

private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) 
	...
	// 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;

  1. 调用processCacheEvicts先看方法是否有@CacheEvict,进行缓存清除。
  2. 调用findCachedItem查询缓存是否有数据
  3. 如果没有缓存数据
    a)收集该方法的@Cacheable(可以有多个)
    b)调用invokeOperation执行用户方法
  4. 如果有缓存数据
    a)如果没有@CachePut,直接调用get方法把结果拿到,因为spring为了做懒处理,返回的是一个Supplier
    b)如果有@CachePut,还是要执行用户的方法invokeOperation
    注意:这里wrapCacheValue、unwrapReturnValue都是为了适配Optional返回值,不用关心
  5. collectPutRequests是收集所有的@CachePut(可以有多个)
  6. 上面收集了@Cacheable + @CachePut,然后调用cachePutRequest.apply把结果再放入缓存
  7. 最终检查是否有@CacheEvict,进行缓存清除

注意:缓存清除开始执行了一次,结束又执行了一次,是因为@CacheEvict用beforeInvocation来控制是代码执行前删除缓存,还是代码执行后删除。默认是代码执行后删除

以上是关于SpringBoot缓存原理的主要内容,如果未能解决你的问题,请参考以下文章

SpringBoot缓存原理

SpringBoot缓存原理

带着新人学springboot的应用01(springboot+mybatis+缓存 上)

SpringBoot整合SpringCache详解

SpringBoot整合SpringCache详解

Redis分片主从哨兵集群,原理详解,集群的配置安装,8大数据类型,springboot整合使用