springCache源码解读
基础介绍
注解介绍
@EnableCaching
表示开启springCache@CacheConfig
设置缓存配置@CacheEvict
清除缓存@CachePut
结果缓存@Cacheable
方法结果缓存
基本使用
源码流程解读
启动配置装配
使用
springCache
的前提是,配置类上有@EnableCaching
注解。可以看该注解的定义,里面有一个@Import(CachingConfigurationSelector.class)
,这个import
会在程序启动的时候扫描到,进而加载类CachingConfigurationSelector
// CachingConfigurationSelector#selectImports 5.3.12 70行
@Override
public String[] selectImports(AdviceMode adviceMode) {
switch (adviceMode) {
case PROXY:
return getProxyImports();
case ASPECTJ:
return getAspectJImports();
default:
return null;
}
}
这里会根据不同的代理模式选择不同的import,我这里的
PROXY
,因此会装载AutoProxyRegistrar
和ProxyCachingConfiguration
这两个类,其中后者会生成一个CacheInterceptor
实例,该实例会对后续的请求进行拦截,然后进行缓存操作。
请求触发
触发点
被其他方法调用会触发方法拦截,bug入口可以从
CacheInterceptor#invoke
开始看。
CacheAspectSupport#execute
protected Object execute(CacheOperationInvoker invoker, Object target, Method method, Object[] args) {
if (this.initialized) {
Class<?> targetClass = getTargetClass(target);
// 获取 cache operations 它们会在启动的时候装配
CacheOperationSource cacheOperationSource = getCacheOperationSource();
if (cacheOperationSource != null) {
// 获取对应的 cache operations 这里大部分能直接拿到,小部分需要计算获取 TODO 什么场景呢
Collection<CacheOperation> operations = cacheOperationSource.getCacheOperations(method, targetClass);
if (!CollectionUtils.isEmpty(operations)) {
// 构造上下文,执行
return execute(invoker, method,
new CacheOperationContexts(operations, method, args, target, targetClass));
}
}
}
return invoker.invoke();
}
获取上下文
public CacheOperationContexts(Collection<? extends CacheOperation> operations, Method method,
Object[] args, Object target, Class<?> targetClass) {
this.contexts = new LinkedMultiValueMap<>(operations.size());
for (CacheOperation op : operations) {
this.contexts.add(op.getClass(), getOperationContext(op, method, args, target, targetClass));
}
// @Cacheable 同步标记
this.sync = determineSyncFlag(method);
}
protected CacheOperationContext getOperationContext(
CacheOperation operation, Method method, Object[] args, Object target, Class<?> targetClass) {
// 获取或构造元数据
CacheOperationMetadata metadata = getCacheOperationMetadata(operation, method, targetClass);
// 构造上下文
return new CacheOperationContext(metadata, args, target);
}
构造元数据
protected CacheOperationMetadata getCacheOperationMetadata(
CacheOperation operation, Method method, Class<?> targetClass) {
// 构造缓存key,先查询=缓存
CacheOperationCacheKey cacheKey = new CacheOperationCacheKey(operation, method, targetClass);
CacheOperationMetadata metadata = this.metadataCache.get(cacheKey);
// 没有则进行构造
if (metadata == null) {
// 判断定义的时候有没有 keyGenerator 有则加载这个类,没有则用通用的
KeyGenerator operationKeyGenerator;
if (StringUtils.hasText(operation.getKeyGenerator())) {
operationKeyGenerator = getBean(operation.getKeyGenerator(), KeyGenerator.class);
}
else {
operationKeyGenerator = getKeyGenerator();
}
// 判断定义的时候有没有 cacheResolver或cacheManager,有则加载定义的类,没有则使用通用的
CacheResolver operationCacheResolver;
if (StringUtils.hasText(operation.getCacheResolver())) {
operationCacheResolver = getBean(operation.getCacheResolver(), CacheResolver.class);
}
else if (StringUtils.hasText(operation.getCacheManager())) {
CacheManager cacheManager = getBean(operation.getCacheManager(), CacheManager.class);
operationCacheResolver = new SimpleCacheResolver(cacheManager);
}
else {
operationCacheResolver = getCacheResolver();
Assert.state(operationCacheResolver != null, "No CacheResolver/CacheManager set");
}
// 构造元数据,加入缓存
metadata = new CacheOperationMetadata(operation, method, targetClass,
operationKeyGenerator, operationCacheResolver);
this.metadataCache.put(cacheKey, metadata);
}
return metadata;
}
构造上下文
public CacheOperationContext(CacheOperationMetadata metadata, Object[] args, Object target) {
this.metadata = metadata;
// 可变参数处理
this.args = extractArgs(metadata.method, args);
this.target = target;
// 根据名字获取对应的cache
this.caches = CacheAspectSupport.this.getCaches(this, metadata.cacheResolver);
// caches 名字集合
this.cacheNames = createCacheNames(this.caches);
}
// 变长入参处理
private Object[] extractArgs(Method method, Object[] args) {
if (!method.isVarArgs()) {
return args;
}
// 如果是可变入参,最后一个参数是数组
Object[] varArgs = ObjectUtils.toObjectArray(args[args.length - 1]);
Object[] combinedArgs = new Object[args.length - 1 + varArgs.length];
// 复制数据
System.arraycopy(args, 0, combinedArgs, 0, args.length - 1);
System.arraycopy(varArgs, 0, combinedArgs, args.length - 1, varArgs.length);
return combinedArgs;
}
execute 核心步骤
private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {
// 处理@Cacbeable 同步操作
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 {
// 查询缓存,缓存命中则包装结果返回,如果没有命中则同步赋值(synchronized)
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());
}
}
// condition没有通过,直接调用方法 注意:这里可以看出设置了sync=true的@Cacheable是独占的,
// 如果在方法层面顶一个多个 @Cache 注解,只有它会生效。
else {
return invokeOperation(invoker);
}
}
// 处理@CacheEvict 前置执行操作
processCacheEvicts(contexts.get(CacheEvictOperation.class), true,
CacheOperationExpressionEvaluator.NO_RESULT);
// 判断是否命中缓存
Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class));
// 收集CachePut操作
List<CachePutRequest> cachePutRequests = new ArrayList<>();
// 如果缓存没有名中国,则将@Cacheable 收集到 cachePutRequests
if (cacheHit == null) {
collectPutRequests(contexts.get(CacheableOperation.class),
CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);
}
Object cacheValue;
Object returnValue;
// 如果缓存命中且没有 CachePut操作。则直接使用缓存
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);
}
//将 @CachePuts 收集到 cachePutRequests
collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);
// 对cachePutRequest执行更新缓存操作(@CachePut和@Cacheable)
for (CachePutRequest cachePutRequest : cachePutRequests) {
cachePutRequest.apply(cacheValue);
}
// 执行 @CacheEvict 后置执行操作
processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);
return returnValue;
}
processCacheEvicts
private void processCacheEvicts(
Collection<CacheOperationContext> contexts, boolean beforeInvocation, @Nullable Object result) {
// 遍历,校验是否是前置执行,并且符合 condition条件
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, @Nullable Object result) {
Object key = null;
for (Cache cache : context.getCaches()) {
// 判断是否是全局缓存(对整个Cache生效)
if (operation.isCacheWide()) {
// 打印日志,移除清空Cache
logInvalidating(context, operation, null);
// 这里有是否立即处理判断,进去看了一下,RedisCache没啥区别。
doClear(cache, operation.isBeforeInvocation());
}
else {
// 构造对应的key,然后把这个key的缓存移除
if (key == null) {
key = generateKey(context, result);
}
logInvalidating(context, operation, key);
doEvict(cache, key, operation.isBeforeInvocation());
}
}
}
findCachedItem
private Cache.ValueWrapper findCachedItem(Collection<CacheOperationContext> contexts) {
Object result = CacheOperationExpressionEvaluator.NO_RESULT;
for (CacheOperationContext context : contexts) {
// condition 条件是否通过,通过的话构造缓存key 查询缓存
if (isConditionPassing(context, result)) {
Object key = generateKey(context, result);
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;
}
collectPutRequests
private void collectPutRequests(Collection<CacheOperationContext> contexts,
@Nullable Object result, Collection<CachePutRequest> putRequests) {
// 遍历,构造key 构造CachePutRequest
for (CacheOperationContext context : contexts) {
if (isConditionPassing(context, result)) {
Object key = generateKey(context, result);
putRequests.add(new CachePutRequest(context, key));
}
}
}
CachePutRequest#apply
public void apply(@Nullable Object result) {
// 对结果进行 unless 校验
if (this.context.canPutToCache(result)) {
// 置入缓存
for (Cache cache : this.context.getCaches()) {
doPut(cache, this.key, result);
}
}
}
// RedisCache#put()
public void put(Object key, @Nullable Object value) {
// 空值校验
Object cacheValue = preProcessCacheValue(value);
if (!isAllowNullValues() && cacheValue == null) {
throw new IllegalArgumentException(String.format(
"Cache '%s' does not allow 'null' values. Avoid storing null via '@Cacheable(unless=\"#result == null\")' or configure RedisCache to allow 'null' via RedisCacheConfiguration.",
name));
}
// 写入redis
cacheWriter.put(name, createAndConvertCacheKey(key), serializeCacheValue(cacheValue), cacheConfig.getTtl());
}
缓存写入
构造缓存key
// RedisCache#createAndConvertCacheKey
private byte[] createAndConvertCacheKey(Object key) {
return serializeCacheKey(createCacheKey(key));
}
protected String createCacheKey(Object key) {
// 转换key
String convertedKey = convertKey(key);
// 如果不使用前缀,则直接返回key
if (!cacheConfig.usePrefix()) {
return convertedKey;
}
// 构造key 最终key格式 CacheName::key 例如 c10m::name
return prefixCacheKey(convertedKey);
}
private String prefixCacheKey(String key) {
return cacheConfig.getKeyPrefixFor(name) + key;
}
// CacheKeyPrefix 最终就是 value::key
static CacheKeyPrefix simple() {
return name -> name + SEPARATOR;
}
写入缓存
// DefaultRedisCacheWriter#put()
public void put(String name, byte[] key, byte[] value, @Nullable Duration ttl) {
Assert.notNull(name, "Name must not be null!");
Assert.notNull(key, "Key must not be null!");
Assert.notNull(value, "Value must not be null!");
// 写入redis
execute(name, connection -> {
if (shouldExpireWithin(ttl)) {
connection.set(key, value, Expiration.from(ttl.toMillis(), TimeUnit.MILLISECONDS), SetOption.upsert());
} else {
connection.set(key, value);
}
return "OK";
});
// redis 实际没啥用
statistics.incPuts(name);
}
private <T> T execute(String name, Function<RedisConnection, T> callback) {
// 可以看到这里使用了 connectionFactory,如果使用了 Jedis,则会从连接池中取连接
RedisConnection connection = connectionFactory.getConnection();
try {
checkAndPotentiallyWaitUntilUnlocked(name, connection);
return callback.apply(connection);
} finally {
connection.close();
}
}
构造缓存key
这里用来处理 @Cache 系列中的
key
属性 里面有sPEL
解析,感兴趣的可以看看。 condition的判断也是类似的。
// CacheAspectSupport#generateKey
protected Object generateKey(@Nullable Object result) {
if (StringUtils.hasText(this.metadata.operation.getKey())) {
EvaluationContext evaluationContext = createEvaluationContext(result);
return evaluator.key(this.metadata.operation.getKey(), this.metadata.methodKey, evaluationContext);
}
return this.metadata.keyGenerator.generate(this.target, this.metadata.method, this.args);
}
拦截器怎么起作用的
// ProxyCachingConfiguration#cacheAdvisor()
// 注意:这里没用默认的名字,用了 org.springframework.cache.config.internalCacheAdvisor
@Bean(name = CacheManagementConfigUtils.CACHE_ADVISOR_BEAN_NAME)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public BeanFactoryCacheOperationSourceAdvisor cacheAdvisor(
CacheOperationSource cacheOperationSource, CacheInterceptor cacheInterceptor) {
BeanFactoryCacheOperationSourceAdvisor advisor = new BeanFactoryCacheOperationSourceAdvisor();
advisor.setCacheOperationSource(cacheOperationSource);
advisor.setAdvice(cacheInterceptor);
if (this.enableCaching != null) {
advisor.setOrder(this.enableCaching.<Integer>getNumber("order"));
}
return advisor;
}
//方法调用触发的时候,会创建代理 AbstractAutoProxyCreator#wrapIfNecessary
总结
CacheOperation
是方法层面的,也就是说一个方法可以同时被@Cacheable
、@CachePut
、@CacheEvict
同时注释Cacheable
如果设置属性sync
为true
,则会使unless
失效。如果方法层面有多个@Cache
注解,则其他的都失效- 如果集成了
redis
,不用害怕使用@Cache
系列不会释放redis
连接,它最终使用的是jedis
连接池。 redis
最终的缓存key
拼凑,CacheName::key
,示例:CacheName = c10m
、key = name
,则最终redis
中的缓存key
为c10m::name