硬编码使用缓存

为了提升商品信息的查询效率,我们对商品信息使用了缓存,示例代码如下:

package com.morris.spring.demo.cache;

import com.morris.spring.entity.Good;
import com.morris.spring.service.GoodService;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.Objects;

@Service
public class GoodCacheDemo {
	
	@Resource
	private RedisTemplate redisTemplate;
	
	@Resource
	private GoodService goodService;
	
	public Good getGoodById(Integer id) {
		String key = "good:" + id;
		Good good = (Good) redisTemplate.opsForValue().get(key);
		if(Objects.nonNull(good)) {
			return good;
		}

		good = goodService.getById(id);
		redisTemplate.opsForValue().set(key, good);
		return good;
	}
	
}

相信很多人都写过类似风格的代码,这种风格符合面向过程的编程思维,非常容易理解。但它也有一些缺点:

  • 代码不够优雅。业务逻辑有四个典型动作:存储,读取,修改,删除。每次操作都需要定义缓存Key ,调用缓存命令的API,产生较多的重复代码;
  • 缓存操作和业务逻辑之间的代码耦合度高,对业务逻辑有较强的侵入性。

侵入性主要体现如下两点:

  • 开发联调阶段,需要去掉缓存,只能注释或者临时删除缓存操作代码,也容易出错;
  • 某些场景下,需要更换缓存组件,每个缓存组件有自己的API,更换成本颇高。

Spring Cache提供的缓存注解

Spring Cache提供了一个对缓存使用的抽象,以及大量的实现方便开发者使用。

要想使用Spring Cache注解,需要在启动类或配置类上需要加上@EnableCaching注解才会生效。

Spring Cache主要提供了如下注解:

注解

说明

@Cacheable

根据方法的请求参数对其结果进行缓存

@CachePut

根据方法的请求参数对其结果进行缓存,和@Cacheable不同的是,它每次都会触发真实方法的调用

@CacheEvict

根据一定的条件对缓存进行清空

缓存注解的使用

配置类

package com.morris.spring.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;

import java.time.Duration;

@PropertySource("classpath:redis.properties")
@Configuration
@EnableCaching // 开启缓存注解
public class CacheConfig {

	@Value("${redis.host}")
	private String hostName;

	@Value("${redis.port}")
	private Integer port;

	@Value("${redis.password}")
	private String password;

	@Bean
	public RedisCacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory) {
		RedisCacheConfiguration redisCacheConfiguration = getRedisCacheConfiguration();
		RedisCacheManager cacheManager = RedisCacheManager
				.builder(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory))
				.cacheDefaults(redisCacheConfiguration)
				.build();

		return cacheManager;
	}

	private RedisCacheConfiguration getRedisCacheConfiguration() {
		Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
		ObjectMapper om = new ObjectMapper();
		om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
		om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.WRAPPER_ARRAY);
		jackson2JsonRedisSerializer.setObjectMapper(om);
		RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
		redisCacheConfiguration = redisCacheConfiguration.serializeValuesWith(
				RedisSerializationContext
						.SerializationPair
						.fromSerializer(jackson2JsonRedisSerializer)
		).entryTtl(Duration.ofDays(7));
		return redisCacheConfiguration;
	}

	@Bean
	public JedisConnectionFactory jedisConnectionFactory() {
		RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(hostName, port);
		redisStandaloneConfiguration.setPassword(password);
		JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(redisStandaloneConfiguration);
		return jedisConnectionFactory;
	}
}

service层的使用

package com.morris.spring.service;

import com.morris.spring.dao.GoodDao;
import com.morris.spring.entity.Good;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;

import javax.annotation.Resource;

public class GoodCacheServiceImpl implements GoodCacheService {

	@Resource
	private GoodDao goodDao;

	@Override
	public boolean insert(Good good) {
		return goodDao.insert(good);
	}

	@Cacheable(cacheNames = "GC", key = "#id", unless = "#result eq null")
	@Override
	public Good getById(Integer id) {
		return goodDao.getById(id);
	}

	@CachePut(cacheNames = "GC", key = "#good.id")
	@Override
	public Good updateById(Good good) {
		goodDao.updateById(good);
		return good;
	}

	@CacheEvict(cacheNames = "GC", key = "#id", condition = "#result eq true")
	@Override
	public boolean deleteById(Integer id) {
		return goodDao.deleteById(id);
	}
}

测试类

package com.morris.spring.demo.cache;

import com.morris.spring.config.CacheConfig;
import com.morris.spring.config.JdbcConfig;
import com.morris.spring.dao.GoodDao;
import com.morris.spring.entity.Good;
import com.morris.spring.service.GoodCacheService;
import com.morris.spring.service.GoodCacheServiceImpl;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import java.math.BigDecimal;

/**
 * 缓存注解的使用
 */
public class CacheDemo {

	public static void main(String[] args) {
		AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
		applicationContext.register(GoodDao.class);
		applicationContext.register(GoodCacheServiceImpl.class);
		applicationContext.register(CacheConfig.class);
		applicationContext.register(JdbcConfig.class);
		applicationContext.refresh();

		GoodCacheService goodCacheService = applicationContext.getBean(GoodCacheService.class);

		Good good = new Good();
		good.setGoodName("Huawei");
		good.setPrice(BigDecimal.valueOf(998));
		goodCacheService.insert(good);

		int id = good.getId();
		// 查数据库
		good = goodCacheService.getById(id);

		// 查缓存
		good = goodCacheService.getById(id);
		System.out.println(good);

		// 更新缓存
		good.setPrice(good.getPrice().add(BigDecimal.ONE));
		goodCacheService.updateById(good);

		// 查数据库
		good = goodCacheService.getById(id);
		System.out.println(good);

		// 更新缓存
		good.setPrice(good.getPrice().add(BigDecimal.ONE));
		goodCacheService.updateById(good);

		// 删除缓存
		goodCacheService.deleteById(id);
	}
}

运行结果如下:

... ...
DEBUG JdbcTemplate:891 - Executing SQL update and returning generated keys
DEBUG JdbcTemplate:609 - Executing prepared SQL statement
DEBUG DataSourceUtils:115 - Fetching JDBC Connection from DataSource
DEBUG DriverManagerDataSource:144 - Creating new JDBC DriverManager Connection to [jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&allowMultiQueries=true&characterEncoding=UTF-8&useFastDateParsing=false&zeroDateTimeBehavior=convertToNull]
DEBUG JdbcTemplate:667 - Executing prepared SQL query
DEBUG JdbcTemplate:609 - Executing prepared SQL statement [select * from t_good where id=?]
DEBUG DataSourceUtils:115 - Fetching JDBC Connection from DataSource
DEBUG DriverManagerDataSource:144 - Creating new JDBC DriverManager Connection to [jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&allowMultiQueries=true&characterEncoding=UTF-8&useFastDateParsing=false&zeroDateTimeBehavior=convertToNull]
DEBUG BeanPropertyRowMapper:300 - Mapping column 'id' to property 'id' of type 'java.lang.Integer'
DEBUG BeanPropertyRowMapper:300 - Mapping column 'good_name' to property 'goodName' of type 'java.lang.String'
DEBUG BeanPropertyRowMapper:300 - Mapping column 'price' to property 'price' of type 'java.math.BigDecimal'
Good(id=4, goodName=Huawei, price=998.00)
DEBUG JdbcTemplate:860 - Executing prepared SQL update
DEBUG JdbcTemplate:609 - Executing prepared SQL statement [update t_good set good_name=?,price=? where id=?]
DEBUG DataSourceUtils:115 - Fetching JDBC Connection from DataSource
DEBUG DriverManagerDataSource:144 - Creating new JDBC DriverManager Connection to [jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&allowMultiQueries=true&characterEncoding=UTF-8&useFastDateParsing=false&zeroDateTimeBehavior=convertToNull]
Good(id=4, goodName=Huawei, price=999.00)
DEBUG JdbcTemplate:860 - Executing prepared SQL update
DEBUG JdbcTemplate:609 - Executing prepared SQL statement [update t_good set good_name=?,price=? where id=?]
DEBUG DataSourceUtils:115 - Fetching JDBC Connection from DataSource
DEBUG DriverManagerDataSource:144 - Creating new JDBC DriverManager Connection to [jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&allowMultiQueries=true&characterEncoding=UTF-8&useFastDateParsing=false&zeroDateTimeBehavior=convertToNull]
DEBUG JdbcTemplate:860 - Executing prepared SQL update
DEBUG JdbcTemplate:609 - Executing prepared SQL statement [delete from t_good where id=?]
DEBUG DataSourceUtils:115 - Fetching JDBC Connection from DataSource
DEBUG DriverManagerDataSource:144 - Creating new JDBC DriverManager Connection to [jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&allowMultiQueries=true&characterEncoding=UTF-8&useFastDateParsing=false&zeroDateTimeBehavior=convertToNull]

从运行结果可以发现第二次getById查询直接走的缓存切面,并没有去查询数据库。updateById每次都会更新数据库记录和缓存,deleteById会删除数据库的记录和缓存。

源码分析

@EnableCaching

@EnableCaching用于向容器中注入相关配置类:

org.springframework.cache.annotation.EnableCaching

@Import(CachingConfigurationSelector.class)
public @interface EnableCaching {

@EnableCaching导入了CachingConfigurationSelector。

org.springframework.cache.annotation.CachingConfigurationSelector

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<>(3);
	result.add(AutoProxyRegistrar.class.getName());
	result.add(ProxyCachingConfiguration.class.getName());
	if (jsr107Present && jcacheImplPresent) {
		result.add(PROXY_JCACHE_CONFIGURATION_CLASS);
	}
	return StringUtils.toStringArray(result);
}

CachingConfigurationSelector注入了AutoProxyRegistrar和ProxyCachingConfiguration。

AutoProxyRegistrar

顾名思义,AutoProxyRegistrar从名称上就可以看出其本质是一个ImportBeanDefinitionRegistrar,而ImportBeanDefinitionRegistrar最重要的方法为registerBeanDefinitions(),这个方法的主要作用就是用方法参数中的registry向容器中注入一些BeanDefinition。

org.springframework.context.annotation.AutoProxyRegistrar#registerBeanDefinitions

–> org.springframework.aop.config.AopConfigUtils#registerAutoProxyCreatorIfNecessary(org.springframework.beans.factory.support.BeanDefinitionRegistry)

–> org.springframework.aop.config.AopConfigUtils#registerAutoProxyCreatorIfNecessary(org.springframework.beans.factory.support.BeanDefinitionRegistry, java.lang.Object)

public static BeanDefinition registerAutoProxyCreatorIfNecessary(
		BeanDefinitionRegistry registry, @Nullable Object source) {

	return registerOrEscalateApcAsRequired(InfrastructureAdvisorAutoProxyCreator.class, registry, source);
}

总结:AutoProxyRegistrar.registerBeanDefinitions()往容器中注入了一个类InfrastructureAdvisorAutoProxyCreator。

这段是不是很熟悉,没错,声明式事务中注入的也是这个InfrastructureAdvisorAutoProxyCreator类,用来生成代理对象。

ProxyCachingConfiguration

ProxyCachingConfiguration向Spring容器中注入了三个Bean:

  1. BeanFactoryCacheOperationSourceAdvisor:切面,用来匹配目标方法和目标类,看那些方法需要实现缓存的增强。
  2. CacheOperationSource:用来解析缓存相关的注解
  3. CacheInterceptor:实现了Advice,是一个通知,实现对目标方法的增强。
@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
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());
		if (this.enableCaching != null) {
			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.configure(this.errorHandler, this.keyGenerator, this.cacheResolver, this.cacheManager);
		interceptor.setCacheOperationSource(cacheOperationSource());
		return interceptor;
	}

}

BeanFactoryCacheOperationSourceAdvisor

BeanFactoryCacheOperationSourceAdvisor是一个切面,切面需要包括通知和切点:

  • Advice通知:CacheInterceptor,由ProxyCachingConfiguration注入。
  • Pointcut切点:内部CacheOperationSourcePointcut类型的属性。

CacheOperationSourcePointcut

Pointcut主要包含两部分,对目标类的匹配,对目标方法的匹配。

对目标类的匹配,其实啥也没干,返回true,主要逻辑在对目标方法的匹配上,大部分Pointcut都是如此:

private class CacheOperationSourceClassFilter implements ClassFilter {

	@Override
	public boolean matches(Class<?> clazz) {
		if (CacheManager.class.isAssignableFrom(clazz)) {
			return false;
		}
		// 啥也没干
		CacheOperationSource cas = getCacheOperationSource();
		return (cas == null || cas.isCandidateClass(clazz));
	}
}

对目标方法的匹配:

public boolean matches(Method method, Class<?> targetClass) {
	CacheOperationSource cas = getCacheOperationSource();
	/**
	 * @see AbstractFallbackCacheOperationSource#getCacheOperations(java.lang.reflect.Method, java.lang.Class)
	 */
	// 就是看目标方法上面有没有缓存注解
	return (cas != null && !CollectionUtils.isEmpty(cas.getCacheOperations(method, targetClass)));
}

这里会委托给CacheOperationSource来解析缓存注解,这个类是在ProxyCachingConfiguration中注入的。

最终会调用到org.springframework.cache.annotation.SpringCacheAnnotationParser#parseCacheAnnotations(org.springframework.cache.annotation.SpringCacheAnnotationParser.DefaultCacheConfig, java.lang.reflect.AnnotatedElement, boolean)

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

	// @Cacheable解析为CacheableOperation
	// @CacheEvict解析为CacheEvictOperation
	// @CachePut解析为CachePutOperation
	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;
}

CacheInterceptor

CacheInterceptor是一个Advice,用来实现对目标方法的增强,当调用目标方法时会先进入CacheInterceptor.invoke()方法:

org.springframework.cache.interceptor.CacheInterceptor#invoke

public Object invoke(final MethodInvocation invocation) throws Throwable {
	Method method = invocation.getMethod();
	// 将目标方法的调用封装为一个lambda表达式
	CacheOperationInvoker aopAllianceInvoker = () -> {
		try {
			// 调用目标方法,后面的代码会回调到这里
			return invocation.proceed();
		}
		catch (Throwable ex) {
			throw new CacheOperationInvoker.ThrowableWrapper(ex);
		}
	};
	try {
		// 调用父类的execute
		return execute(aopAllianceInvoker, invocation.getThis(), method, invocation.getArguments());
	}
	catch (CacheOperationInvoker.ThrowableWrapper th) {
		throw th.getOriginal();
	}
}

org.springframework.cache.interceptor.CacheAspectSupport#execute(org.springframework.cache.interceptor.CacheOperationInvoker, java.lang.Object, java.lang.reflect.Method, java.lang.Object[])

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)
	// initialized会在SmartInitializingSingleton.afterSingletonsInstantiated()改为true
	if (this.initialized) {
		Class<?> targetClass = getTargetClass(target);
		// 负责解析注解
		CacheOperationSource cacheOperationSource = getCacheOperationSource();
		if (cacheOperationSource != null) {
			/**
			 * 解析@Cacheable为CacheableOperation
			 * 解析@CacheEvict为CacheEvictOperation
			 * 解析CachePut为CachePutOperation
			 *
			 * @see AbstractFallbackCacheOperationSource#getCacheOperations(java.lang.reflect.Method, java.lang.Class)
			 */
			// 这里不会真正去解析缓存注解了,因为前面解析过了,直接从缓存中取
			Collection<CacheOperation> operations = cacheOperationSource.getCacheOperations(method, targetClass);
			if (!CollectionUtils.isEmpty(operations)) {
				// new CacheOperationContexts 将切面执行过程中需要用到的对象封装为CacheOperationContexts
				return execute(invoker, method,
						new CacheOperationContexts(operations, method, args, target, targetClass));
			}
		}
	}
	// 目标方法上面没有缓存注解,直接调用目标方法
	return invoker.invoke();
}

org.springframework.cache.interceptor.CacheAspectSupport#execute(org.springframework.cache.interceptor.CacheOperationInvoker, java.lang.reflect.Method, org.springframework.cache.interceptor.CacheAspectSupport.CacheOperationContexts)

private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {
	// Special handling of synchronized invocation
	if (contexts.isSynchronized()) {
		// 方法上面有@Cacheable(sync=true)才会进入
		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 {
				// 调用cache.get(Object key, Callable<T> valueLoader)
				return wrapCacheValue(method, handleSynchronizedGet(invoker, key, cache));
			}
			catch (Cache.ValueRetrievalException ex) {
				// Directly propagate ThrowableWrapper from the invoker,
				// or potentially also an IllegalArgumentException etc.
				ReflectionUtils.rethrowRuntimeException(ex.getCause());
			}
		}
		else {
			// No caching required, only call the underlying method
			// condtion不满足,直接调用目标方法
			return invokeOperation(invoker);
		}
	}
	// Process any early evictions
	// 先调用@CacheEvict注解中的属性beforeInvocation=true的,实际上这个值默认为false,这里一般不会干啥,看后面的调用
	// cache.evict()
	processCacheEvicts(contexts.get(CacheEvictOperation.class), true,
			CacheOperationExpressionEvaluator.NO_RESULT);
	// Check if we have a cached item matching the conditions
	// cache.get()
	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<>();
	if (cacheHit == null) {
		// 收集CacheableOperation,也就是@Cacheable
		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
		// 缓存中有,并且没有@CachePut注解,就可以直接走缓存,不用调用目标方法
		cacheValue = cacheHit.get();
		returnValue = wrapCacheValue(method, cacheValue);
	}
	else {
		// Invoke the method if we don't have a cache hit
		// 有@CachePut注解,需要调用目标方法
		returnValue = invokeOperation(invoker);
		cacheValue = unwrapReturnValue(returnValue);
	}
	// Collect any explicit @CachePuts
	// 收集@CachePut
	collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);
	// Process any collected put requests, either from @CachePut or a @Cacheable miss
	for (CachePutRequest cachePutRequest : cachePutRequests) {
		// 处理@CachePut和@Cacheable
		cachePutRequest.apply(cacheValue);
	}
	// Process any late evictions
	// 调用@CacheEvict注解中的属性beforeInvocation=false的
	processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);
	return returnValue;
}

同步处理

org.springframework.cache.interceptor.CacheAspectSupport#handleSynchronizedGet

private Object handleSynchronizedGet(CacheOperationInvoker invoker, Object key, Cache cache) {
	InvocationAwareResult invocationResult = new InvocationAwareResult();
	// get(Object key, Callable<T> valueLoader)
	// 在get()中可以自定义同步策略来防止缓存击穿,可使用synchronized或分布式锁
	Object result = cache.get(key, () -> {
		invocationResult.invoked = true;
		if (logger.isTraceEnabled()) {
			logger.trace("No cache entry for key '" + key + "' in cache " + cache.getName());
		}
		return unwrapReturnValue(invokeOperation(invoker));
	});
	if (!invocationResult.invoked && logger.isTraceEnabled()) {
		logger.trace("Cache entry for key '" + key + "' found in cache '" + cache.getName() + "'");
	}
	return result;
}

从缓存中查找元素

org.springframework.cache.interceptor.CacheAspectSupport#findCachedItem

private Cache.ValueWrapper findCachedItem(Collection<CacheOperationContext> contexts) {
	Object result = CacheOperationExpressionEvaluator.NO_RESULT;
	for (CacheOperationContext context : contexts) {
		// 处理注解中的condtion属性
		if (isConditionPassing(context, result)) {
			Object key = generateKey(context, result);
			// 调用cache.get()
			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;
}

private Cache.ValueWrapper findInCaches(CacheOperationContext context, Object key) {
	for (Cache cache : context.getCaches()) {
		// cache.get(key)
		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;
}

将元素放入缓存中

org.springframework.cache.interceptor.CacheAspectSupport.CachePutRequest#apply

public void apply(@Nullable Object result) {
	// canPutToCache校验unless属性
	if (this.context.canPutToCache(result)) {
		for (Cache cache : this.context.getCaches()) {
			doPut(cache, this.key, result);
		}
	}
}

处理缓存失效

org.springframework.cache.interceptor.CacheAspectSupport#processCacheEvicts

private void processCacheEvicts(
		Collection<CacheOperationContext> contexts, boolean beforeInvocation, @Nullable Object result) {
	for (CacheOperationContext context : contexts) {
		CacheEvictOperation operation = (CacheEvictOperation) context.metadata.operation;
		// 先看@CacheEvict注解中的属性beforeInvocation,默认为false
		if (beforeInvocation == operation.isBeforeInvocation() && isConditionPassing(context, result)) {
			// 调用cache.evict(),cache.clear()清除缓存
			performCacheEvict(context, operation, result);
		}
	}
}