Spring boot cache principle

Posted by anon on Sat, 11 Dec 2021 14:41:58 +0100

Three notes

@Cacheable can be marked on a method or a class. When marked on a method, it means that the method supports caching. When marked on a class, it means that all methods of the class support caching. For a method that supports caching, spring will cache its return value after it is called to ensure that the next time the method is executed with the same parameters, the results can be obtained directly from the cache without executing the method again. Spring caches the return value of a method as a key value pair. The value is the return result of the method. As for the key, spring supports two strategies, the default strategy and the user-defined strategy. It should be noted that when a method supporting caching is called inside the object, the caching function will not be triggered.

@CachePut can also declare a method to support caching. Unlike @ Cacheable, the method marked with @ CachePut does not check whether there are previously executed results in the cache before execution, but executes the method every time and stores the execution results in the specified cache in the form of key value pairs.

@CacheEvict is used to mark on the method or class that needs to clear cache elements. When marked on a class, it means that the execution of all methods in it will trigger the cache cleanup operation. Use beforeInvocation to change the time when the purge operation is triggered. When we specify that the attribute value is true, Spring will clear the specified element in the cache before calling this method.

to configure

<?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>

Cache bottom

It can be seen that Cacheable uses SimpleCacheManager (Cache Management), which contains a cache container caches. spring provides a cache factory ConcurrentMapCacheFactoryBean by default
The cache container actually used is: ConcurrentMapCache

public class ConcurrentMapCache extends AbstractValueAdaptingCache {

	private final String name;

	private final ConcurrentMap<Object, Object> store;

ConcurrentMapCache simply encapsulates a layer of ConcurrentMap and does not provide additional excellent functions (scheduled deletion, capacity limitation...)

Section principle

Route: MVC - > spring AOP - > cglib - > spring cache
Spring cache is a general knowledge before, which is beyond the scope of this article. Start with spring cache
Core code: 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. Call processCacheEvicts to see if the method has @ CacheEvict and clear the cache.
  2. Call findCachedItem to query whether there is data in the cache
  3. If there is no cached data
    a) Collect @ Cacheable of this method (there can be multiple)
    b) Invoke invokeOperation to execute the user method
  4. If there is cached data
    a) If there is no @ CachePut, directly call the get method to get the result, because spring returns a Supplier for lazy processing
    b) If there is @ CachePut, execute the user's method invokeOperation
    Note: wrapCacheValue and unwrapReturnValue here are used to adapt to the Optional return value. Don't worry
  5. collectPutRequests is to collect all @ CachePut (there can be multiple)
  6. It collects @Cacheable + @CachePut and then calls cachePutRequest.. Apply puts the results back into the cache
  7. Finally, check whether there is @ CacheEvict and clear the cache

Note: cache cleanup starts once and ends again because @ CacheEvict uses beforeInvocation to control whether to delete the cache before or after code execution. The default is delete after code execution

Topics: Java Spring Spring Boot Cache