Spring Cache(一) - Cache在springboot中的实现与原理

2,574 阅读6分钟

引言

本文只讨论spring-cache在springboot中是如何实现的,具体的使用方法网上有很多,这里就不详细描述了. 本文中需要涉及到这些类,先列举出来.

  • @EnableCaching: cache的入口,用于开启spring-cache
  • CachingConfigurationSelector 选择使用哪种AbstractCachingConfiguration
  • ProxyCachingConfiguration 基于注解的缓存配置
  • CachingConfigurerSupport:缓存配置支持类,需要使用spring cache的项目继承,一个项目只能有一个相关bean。
  • AbstractCachingConfiguration:抽象缓存配置,用于初始化配置。加载cacheManager、 cacheResolver、 keyGenerator
  • ProxyCachingConfiguration : 继承AbstractCachingConfiguration,默认的缓存代理配置,用来配置拦截器。
  • CacheInterceptor 缓存的切面拦截器
  • CacheAspectSupport 缓存方面的基类,例如CacheInterceptor或AspectJ方面。这使基础的Spring缓存基础结构可以轻松地用于为任何方面系统实现方面
  • Cache 缓存的抽象, 定义了缓存的一些操作行为
  • CacheManager 缓存管理器,获取缓存的接口

1:启动干了些啥

  1. 添加@EnableCaching, 这个注解会@Import一个CachingConfigurationSelector类.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(CachingConfigurationSelector.class)
public @interface EnableCaching {
	
}
  1. CachingConfigurationSelectorselectImports方法中会根据注解中的AdviceMode属性去判断到底走PROXY还是ASPECTJ
@Override
public String[] selectImports(AdviceMode adviceMode) {
    switch (adviceMode) {
    	//这里默认走的是PROXY
        case PROXY:
            return getProxyImports();
        case ASPECTJ:
            return getAspectJImports();
        default:
            return null;
    }
}
  1. selectImports方法中又返回了两个类的名称,这里会去加载这两个类
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);
}
  1. 先来看下AutoProxyRegistrarregisterBeanDefinitions方法干了些什么
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    boolean candidateFound = false;
    Set<String> annTypes = importingClassMetadata.getAnnotationTypes();
    for (String annType : annTypes) {
        AnnotationAttributes candidate = AnnotationConfigUtils.attributesFor(importingClassMetadata, annType);
        if (candidate == null) {
            continue;
        }
        Object mode = candidate.get("mode");
        Object proxyTargetClass = candidate.get("proxyTargetClass");
        if (mode != null && proxyTargetClass != null && AdviceMode.class == mode.getClass() &&
                Boolean.class == proxyTargetClass.getClass()) {
            candidateFound = true;
            if (mode == AdviceMode.PROXY) {
            	//如果mode的模式是PROXY的话则向容器注册一个自动代理创建器,这里是InfrastructureAdvisorAutoProxyCreator实现的
                AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
                if ((Boolean) proxyTargetClass) {
                    AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
                    return;
                }
            }
        }
    }
    if (!candidateFound && logger.isInfoEnabled()) {
        String name = getClass().getSimpleName();
        logger.info(String.format("%s was imported but no annotations were found " +
                "having both 'mode' and 'proxyTargetClass' attributes of type " +
                "AdviceMode and boolean respectively. This means that auto proxy " +
                "creator registration and configuration may not have occurred as " +
                "intended, and components may not be proxied as expected. Check to " +
                "ensure that %s has been @Import'ed on the same class where these " +
                "annotations are declared; otherwise remove the import of %s " +
                "altogether.", name, name, name));
    }
}
@Nullable
public static BeanDefinition registerAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry) {
    return registerAutoProxyCreatorIfNecessary(registry, null);
}

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

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

可以看出创建了一个InfrastructureAdvisorAutoProxyCreator类型的BeanDefinition 它是一个AbstractAdvisorAutoProxyCreator,默认扫描所有Advisor(切面)的类,判断是否能代理,对能代理的类生成代理.

  1. 再来看下ProxyCachingConfiguration
@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyCachingConfiguration extends AbstractCachingConfiguration {

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

	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public CacheOperationSource cacheOperationSource() {
		return new AnnotationCacheOperationSource();
	}

	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public CacheInterceptor cacheInterceptor(CacheOperationSource cacheOperationSource) {
		CacheInterceptor interceptor = new CacheInterceptor();
		interceptor.configure(this.errorHandler, this.keyGenerator, this.cacheResolver, this.cacheManager);
		interceptor.setCacheOperationSource(cacheOperationSource);
		return interceptor;
	}

}

可以看出这里实例化了三个bean 分别是BeanFactoryCacheOperationSourceAdvisor, CacheOperationSource, CacheInterceptor.

先来说说BeanFactoryCacheOperationSourceAdvisor, 它继承于AbstractBeanFactoryPointcutAdvisor,作用是在每个bean的初始化时 (每个bean都会被加载成 advised 对象 -> 有 targetSource 和 Advisor[] 数组),每个bean被调用方法的时候都是先遍历advisor的方法,然后在调用原生bean(也就是targetSource)的方法,实现了aop的效果;

bean 加载的时候 BeanFactoryCacheOperationSourceAdvisorgetPointcut() 也就是 CacheOperationSourcePointcut 就会被获取,然后调用CacheOperationSourcePointcut.matches()方法, 用来匹配对应的bean, 假设bean在BeanFactoryCacheOperationSourceAdvisor的扫描中 matchs() 方法返回了true, 结果就是在每个bean的方法被调用的时候 CacheInterceptor 中的 invoke() 方法就会被调用

再来CacheOperationSource 是一个接口, 实现类是AnnotationCacheOperationSource, 父类是AbstractFallbackCacheOperationSource

父类AbstractFallbackCacheOperationSourcegetCacheOperations方法

@Override
@Nullable
public Collection<CacheOperation> getCacheOperations(Method method, @Nullable Class<?> targetClass) {
    if (method.getDeclaringClass() == Object.class) {
        return null;
    }

    Object cacheKey = getCacheKey(method, targetClass);
    Collection<CacheOperation> cached = this.attributeCache.get(cacheKey);

    if (cached != null) {
        return (cached != NULL_CACHING_ATTRIBUTE ? cached : null);
    }
    else {
    	//这里去找目标方法上的注解,如@Cachable @CachePut 等
        Collection<CacheOperation> cacheOps = computeCacheOperations(method, targetClass);
        if (cacheOps != null) {
            if (logger.isTraceEnabled()) {
                logger.trace("Adding cacheable method '" + method.getName() + "' with attribute: " + cacheOps);
            }
            this.attributeCache.put(cacheKey, cacheOps);
        }
        else {
            this.attributeCache.put(cacheKey, NULL_CACHING_ATTRIBUTE);
        }
        return cacheOps;
    }
}

这里其实就是对attributeCache 的封装使用,通过把 method - CacheOperation 然后在 CacheInterceptor.invoke() 的时候通过invocation 获取到 method-class 然后调用 CacheOperationSource.getCacheOperations() 获取到 CacheOperation, CacheOperation 其实就是触发对应spring-cache 注解的操作-获取缓存的实现了

最后是AnnotationCacheOperationSource, 它的findCacheOperations()方法, 也就是用来解析是否要拦截该方法(@Cachable @CachePut)等注解标记的方法

启动解析部分就差不多了.下边就是在一个被例如@Cachable注解的方法执行流程了

2:执行流程

  1. 被注解如@Cachable标记的方法会进入CacheInterceptor.invoke()方法
public class CacheInterceptor extends CacheAspectSupport implements MethodInterceptor, Serializable {

  @Override
  @Nullable
  public Object invoke(final MethodInvocation invocation) throws Throwable {
      Method method = invocation.getMethod();

      CacheOperationInvoker aopAllianceInvoker = () -> {
          try {
              return invocation.proceed();
          }
          catch (Throwable ex) {
              throw new CacheOperationInvoker.ThrowableWrapper(ex);
          }
      };

      Object target = invocation.getThis();
      Assert.state(target != null, "Target must not be null");
      try {
          return execute(aopAllianceInvoker, target, method, invocation.getArguments());
      }
      catch (CacheOperationInvoker.ThrowableWrapper th) {
          throw th.getOriginal();
      }
  }

}
  1. 然后调用父类CacheAspectSupport.execute()方法
@Nullable
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)
    if (this.initialized) {
        Class<?> targetClass = getTargetClass(target);
        CacheOperationSource cacheOperationSource = getCacheOperationSource();
        if (cacheOperationSource != null) {
        	//根据Class+Method获取 Collection<CacheOperation>
            Collection<CacheOperation> operations = cacheOperationSource.getCacheOperations(method, targetClass);
            if (!CollectionUtils.isEmpty(operations)) {
                return execute(invoker, method,
                		//注意这个对象在创建的时候会将已经确定的Cache等信息构建好。
                        new CacheOperationContexts(operations, method, args, target, targetClass));
            }
        }
    }

    return invoker.invoke();
}

其中new CacheOperationContexts(operations, method, args, target, targetClass)需要注意下,最终创建的这个对象会从CacheManager#getCache方法中根据cacheName找到对应的cache。

public CacheOperationContext(CacheOperationMetadata metadata, Object[] args, Object target) {
    this.metadata = metadata;
    this.args = extractArgs(metadata.method, args);
    this.target = target;
    //这里去根据找CacheManager中找对应的Cache
    this.caches = CacheAspectSupport.this.getCaches(this, metadata.cacheResolver);
    this.cacheNames = createCacheNames(this.caches);
}

....

protected Collection<? extends Cache> getCaches(
        CacheOperationInvocationContext<CacheOperation> context, CacheResolver cacheResolver) {

	//根据CacheResolver去找对应的caches
    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.");
    }
    return caches;
}
....

@Override
public Collection<? extends Cache> resolveCaches(CacheOperationInvocationContext<?> context) {
    Collection<String> cacheNames = getCacheNames(context);
    if (cacheNames == null) {
        return Collections.emptyList();
    }
    Collection<Cache> result = new ArrayList<>(cacheNames.size());
    for (String cacheName : cacheNames) {
    	//在CacheManager中根据cacheName找到对应的Cache。
        Cache cache = getCacheManager().getCache(cacheName);
        if (cache == null) {
            throw new IllegalArgumentException("Cannot find cache named '" +
                    cacheName + "' for " + context.getOperation());
        }
        result.add(cache);
    }
    return result;
}

继续执行

/*
*  这个方法可以看做是处理@Cacheable, @CachePut, @CacheEvict @Caching这些注解的方法
*/
@Nullable
private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {
    // Special handling of synchronized invocation
    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 {
                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
            return invokeOperation(invoker);
        }
    }


    // 被@CacheEvict注解, 且属性beforeInvocation = true,表示需要在调用前执行清除缓存, 调用Cache的doGet方法或者			//doClear方法
    processCacheEvicts(contexts.get(CacheEvictOperation.class), true,
            CacheOperationExpressionEvaluator.NO_RESULT);

    // 尝试去缓存中获取数据, 调用Cache的doGet方法
    Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class));

    // 如果没有命中缓存, 那么就构建需要缓存的数据包装对象,准备在执行真实方法后将数据放入缓存中
    List<CachePutRequest> cachePutRequests = new ArrayList<>();
    if (cacheHit == null) {
    	//如果被@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
        cacheValue = cacheHit.get();
        returnValue = wrapCacheValue(method, cacheValue);
    }
    else {
        // 没有命中缓存, 则执行目标方法
        returnValue = invokeOperation(invoker);
        cacheValue = unwrapReturnValue(returnValue);
    }

    // 如果被@CachePut 标记,那么需要缓存,收集起来
    collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);

    // 如果有需要被放入缓存的数据信息,那么就将其放入缓存, 调用Cache的doPut方法
    for (CachePutRequest cachePutRequest : cachePutRequests) {
        cachePutRequest.apply(cacheValue);
    }

    // 被@CacheEvict注解, 且属性beforeInvocation = false, 则在方法调用后执行清除缓存
    processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);

    return returnValue;
}
  1. 上边的doGet, doPut等操作都定义在org.springframework.cache#Cache接口中,有很多实现,如ConcurrentMapCache,EhCacheCache, RedisCache等等.这些Cache中都自定义了自己的缓存实现.

  2. 我们在用注解式缓存的时候如:@Cacheable,@CachePut,@CacheEvict的时候, 底层是怎么实现的呢,这里我们拿RedisCache 来举个栗子

public class RedisCache extends AbstractValueAdaptingCache {

  /*
  * 被@Cacheable注解会执行该方法, 从缓存中拿数据.
  */
  @Override
  @Nullable
  public ValueWrapper get(Object key) {
    return toValueWrapper(lookup(key));
  }
  
  /*
   * 放入缓存, 对应@CachePut或者被@Cacheable注解
   * @see org.springframework.cache.Cache#put(java.lang.Object, java.lang.Object)
   */
  @Override
  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));
      }

      cacheWriter.put(name, createAndConvertCacheKey(key), serializeCacheValue(cacheValue), cacheConfig.getTtl());
  }
}

/*
 * 清除缓存 对应@CacheEvict
 * @see org.springframework.cache.Cache#evict(java.lang.Object)
 */
@Override
public void evict(Object key) {
    cacheWriter.remove(name, createAndConvertCacheKey(key));
}