Spring Cache 带你飞(一)
Spring 3.1 版本引入基于 annotation 的 cache 技术,提供了一套抽象的缓存实现方案,通过注解方式使用缓存,基于配置的方式灵活使用不同缓存组件。代码具有相当的灵活性和扩展性,本文基于 Spring 5.x 源码一起分析 Spring Cache 的代码艺术。
开启 Spring Cache
想让 Spring 提供 Cache 能力很简单,只需要在启动类加上 @EnableCaching 注解即可:
@Configuration
@EnableCaching
public class ServerMain {
}
通过在启动类上添加 EnableCaching 注解将 Cache 相关的组件注入到 Spring 启动中,通过 Proxy 或者 AspectJ 的方式获取 Cache 对应的执行信息。
如果你希望在启动时修改一些 Cache 底层对应的基础管理信息,可以通过在启动类上覆盖 CachingConfigurerSupport 提供的相关方法来实现:
@EnableCaching
@SpringBootApplication
public class WebDemoApplication extends CachingConfigurerSupport {
public static void main(String[] args) {
SpringApplication.run(WebDemoApplication.class, args);
}
@Override
public CacheManager cacheManager() {
return super.cacheManager();
}
@Override
public KeyGenerator keyGenerator() {
return super.keyGenerator();
}
}
上面的示例代码中重写了 CacheManager 和 KeyGenerator 两个类的构建实现,分别实现的功能是 Cache 管理方式和 Cache key 生成方式。
从 Bean 加载机制了解 Spring 的 Cache 执行管理
从启动类入手我们很容易看到整体的 Cache 管理方式:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({CachingConfigurationSelector.class})
public @interface EnableCaching {
boolean proxyTargetClass() default false;
AdviceMode mode() default AdviceMode.PROXY;
int order() default 2147483647;
}
-
proxyTargetClass:false,表示使用 JDK 代理,true 表示使用 cglib 代理。
-
mode:指定 AOP 的模式,当值为 AdviceMode.PROXY 时表示使用 Spring aop,当值为当值为AdviceMode.ASPECTJ 时,表示使用 AspectJ。
EnableCaching 使用时导入 CachingConfigurationSelector 类,我们看看加载了什么信息:
public class CachingConfigurationSelector extends AdviceModeImportSelector<EnableCaching> {
public String[] selectImports(AdviceMode adviceMode) {
switch(adviceMode) {
case PROXY:
return this.getProxyImports();
case ASPECTJ:
return this.getAspectJImports();
default:
return null;
}
}
......
......
......
static {
ClassLoader classLoader = CachingConfigurationSelector.class.getClassLoader();
jsr107Present = ClassUtils.isPresent("javax.cache.Cache", classLoader);
jcacheImplPresent = ClassUtils.isPresent("org.springframework.cache.jcache.config.ProxyJCacheConfiguration", classLoader);
}
}
可以看到主要做了一件事,通过代理方式的不同加载对应的 CacheConfiguration :
- 如果是 JDK Proxy,加载 AutoProxyRegistrar 类和 ProxyCachingConfiguration 类;
- 如果是 AspectJ,则加载 AspectJCachingConfiguration 类。
Spring Boot 默认使用 JDK Proxy,我们就看 JDK Proxy 的使用。
AutoProxyRegistrar 的作用就是创建代理对象,内部通过调用 AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry) 方法在 IOC 容器中注册一个 AutoProxyCreator。最终注入到容器中的 AutoProxyRegistrar 是一个 InfrastructureAdvisorAutoProxyCreator 类型:
@Nullable
public static BeanDefinition registerAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry, @Nullable Object source) {
return registerOrEscalateApcAsRequired(InfrastructureAdvisorAutoProxyCreator.class, registry, source);
}
InfrastructureAdvisorAutoProxyCreator 类型只会为基础设施类型的 Advisor 自动创建代理对象。它只会去找符合条件的 bean创建代理,从源码可见只有 role 为 BeanDefinition.ROLE_INFRASTRUCTURE 的满足条件。
ProxyCachingConfiguration 创建了 3 个 bean,CacheOperationSource 关注如何获取所有拦截的切面,
CacheInterceptor 解决要对拦截到的切面做什么。
public class ProxyCachingConfiguration extends AbstractCachingConfiguration {
@Bean(
name = {"org.springframework.cache.config.internalCacheAdvisor"}
)
@Role(2)
public BeanFactoryCacheOperationSourceAdvisor cacheAdvisor(CacheOperationSource cacheOperationSource, CacheInterceptor cacheInterceptor) {
BeanFactoryCacheOperationSourceAdvisor advisor = new BeanFactoryCacheOperationSourceAdvisor();
advisor.setCacheOperationSource(cacheOperationSource);
advisor.setAdvice(cacheInterceptor);
if (this.enableCaching != null) {
advisor.setOrder((Integer)this.enableCaching.getNumber("order"));
}
return advisor;
}
......
}
BeanFactoryCacheOperationSourceAdvisor 是 Spring Cache 自己实现的 Advisor,会对所有能取出 CacheOperation 的方法执行 CacheInterceptor 这个 Advice。
小知识:
Spring AOP 的创建过程本质是实现一个 BeanPostProcessor,在创建 bean 的过程中创建 Proxy,并且为 Proxy 绑定所有适用于该 bean 的 advisor,最终暴露给容器。
Spring 中 AOP 几个关键的概念 advisor, advice, pointcut
advice = 切面拦截中插入的行为
pointcut = 切面的切入点
advisor = advice + pointcut
BeanFactoryCacheOperationSourceAdvisor内部的切入点实现类是 CacheOperationSourcePointcut,切入的逻辑如下:
abstract class CacheOperationSourcePointcut extends StaticMethodMatcherPointcut implements Serializable {
private class CacheOperationSourceClassFilter implements ClassFilter {
private CacheOperationSourceClassFilter() {
}
public boolean matches(Class<?> clazz) {
if (CacheManager.class.isAssignableFrom(clazz)) {
return false;
} else {
CacheOperationSource cas = CacheOperationSourcePointcut.this.getCacheOperationSource();
return cas == null || cas.isCandidateClass(clazz);
}
}
}
}
可以看到切入点主要就是判断被切入的方法上是否有注解:CacheOperationSourcePointcut.this.getCacheOperationSource()。
ProxyCachingConfiguration 中还有另一个值得关注的类 - AnnotationCacheOperationSource:
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public CacheOperationSource cacheOperationSource() {
return new AnnotationCacheOperationSource();
}
CacheOperationSource 持有一个 CacheAnnotationParser 列表。 CacheAnnotationParser 只有一个实现类:SpringCacheAnnotationParser:
public class SpringCacheAnnotationParser implements CacheAnnotationParser, Serializable {
private static final Set<Class<? extends Annotation>> CACHE_OPERATION_ANNOTATIONS = new LinkedHashSet<>(8);
static {
CACHE_OPERATION_ANNOTATIONS.add(Cacheable.class);
CACHE_OPERATION_ANNOTATIONS.add(CacheEvict.class);
CACHE_OPERATION_ANNOTATIONS.add(CachePut.class);
CACHE_OPERATION_ANNOTATIONS.add(Caching.class);
}
......
@Nullable
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;
}
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;
}
......
}
可以看到 Parser 的作用就是将注解对应的方法解析成 CacheOperation 存起来。
看到这里我们开始有点眉目,开始知道去哪里找拦截了什么和操作了什么。接下来继续对 CacheOperation 和 CacheInterceptor 进行深入。
CacheOperation
CacheOperationSource 接口中只有一个方法:
public interface CacheOperationSource {
default boolean isCandidateClass(Class<?> targetClass) {
return true;
}
@Nullable
Collection<CacheOperation> getCacheOperations(Method method, @Nullable Class<?> targetClass);
}
该接口中只有一个方法:通过接口和类名获得对应的 CacheOperation。CacheOperation 是对缓存操作的抽象封装,它的实现类有 3 个:
三个实现类分别对应着 @CacheEvict、@CachePut 和 @Cacheable 注解。
CacheInterceptor
CacheInterceptor 实现的功能就是对 目标方法的实际拦截操作:
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();
}
}
}
CacheInterceptor 代码很简洁,采用函数的形式封装了真正要执行的函数逻辑,最终把此函数传交给父类的 execute()去执行。很显然最终执行目标方法的是 invocation.proceed()。我们直接去父类 CacheAspectSupport 看相关代码逻辑:
public abstract class CacheAspectSupport extends AbstractCacheInvoker
implements BeanFactoryAware, InitializingBean, SmartInitializingSingleton {
protected final Log logger = LogFactory.getLog(getClass());
private final Map<CacheOperationCacheKey, CacheOperationMetadata> metadataCache = new ConcurrentHashMap<>(1024);
private final CacheOperationExpressionEvaluator evaluator = new CacheOperationExpressionEvaluator();
@Nullable
private CacheOperationSource cacheOperationSource;
private SingletonSupplier<KeyGenerator> keyGenerator = SingletonSupplier.of(SimpleKeyGenerator::new);
@Nullable
private SingletonSupplier<CacheResolver> cacheResolver;
......
}
上面摘录出 的一些属性:
Map<CacheOperationCacheKey, CacheOperationMetadata> metadataCache
这个 Map 缓存了所有被注解修饰的类或者方法对应的基本属性信息。
CacheOperationExpressionEvaluator evaluator
解析一些 condition、key、unless 等可以写 el 表达式的处理器。
SingletonSupplier<KeyGenerator> keyGenerator
key 生成器默认使用的 SimpleKeyGenerator,注意 SingletonSupplier 是 Spring5.1 的新类,实现了接口java.util.function.Supplier ,主要是对 null 值进行容错。
接着看相关的方法:
public abstract class CacheAspectSupport extends AbstractCacheInvoker
implements BeanFactoryAware, InitializingBean, SmartInitializingSingleton {
// 这个接口来自于 SmartInitializingSingleton 在实例化完所有单例Bean后调用
//可以看到在这里实例化 CacheManager, CacheManager 我们后面会说作用
@Override
public void afterSingletonsInstantiated() {
if (getCacheResolver() == null) {
// Lazily initialize cache resolver via default cache manager...
Assert.state(this.beanFactory != null, "CacheResolver or BeanFactory must be set on cache aspect");
try {
setCacheManager(this.beanFactory.getBean(CacheManager.class));
}
catch (NoUniqueBeanDefinitionException ex) {
throw new IllegalStateException("No CacheResolver specified, and no unique bean of type " +
"CacheManager found. Mark one as primary or declare a specific CacheManager to use.", ex);
}
catch (NoSuchBeanDefinitionException ex) {
throw new IllegalStateException("No CacheResolver specified, and no bean of type CacheManager found. " +
"Register a CacheManager bean or remove the @EnableCaching annotation from your configuration.", ex);
}
}
this.initialized = true;
}
//根据CacheOperation 封装CacheOperationMetadata
protected CacheOperationMetadata getCacheOperationMetadata(
CacheOperation operation, Method method, Class<?> targetClass) {
CacheOperationCacheKey cacheKey = new CacheOperationCacheKey(operation, method, targetClass);
CacheOperationMetadata metadata = this.metadataCache.get(cacheKey);
if (metadata == null) {
KeyGenerator operationKeyGenerator;
if (StringUtils.hasText(operation.getKeyGenerator())) {
operationKeyGenerator = getBean(operation.getKeyGenerator(), KeyGenerator.class);
}
else {
operationKeyGenerator = getKeyGenerator();
}
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;
}
//真正执行目标方法+ 缓存 的实现
@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)
// 如果已经初始化过(有CacheManager,CacheResolver),执行这里
if (this.initialized) {
//getTargetClass拿到原始Class 解剖代理
Class<?> targetClass = getTargetClass(target);
//简单的说就是拿到该方法上所有的CacheOperation缓存操作,最终一个一个的执行
CacheOperationSource cacheOperationSource = getCacheOperationSource();
if (cacheOperationSource != null) {
// CacheOperationContexts是非常重要的一个私有内部类
// 注意它是复数!不是CacheOperationContext单数
// 所以它就像持有多个注解上下文一样 一个个执行
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();
}
//内部调用的 execute 方法
@Nullable
private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {
// 判断是否同步执行
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);
}
}
// sync=false的情况,走这里
// Process any early evictions beforeInvocation=true的会在此处最先执行~~~
// 最先处理@CacheEvict注解~~~真正执行的方法请参见:performCacheEvict
// context.getCaches()拿出所有的caches,看看是执行cache.evict(key);方法还是cache.clear();
// 需要注意的的是context.isConditionPassing(result); condition条件此处生效,并且可以使用#result
// context.generateKey(result)也能使用#result
// @CacheEvict没有unless属性
processCacheEvicts(contexts.get(CacheEvictOperation.class), true,
CacheOperationExpressionEvaluator.NO_RESULT);
// 执行@Cacheable 看看缓存是否能够命中
Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class));
// 如果缓存没有命中,那就准备一个cachePutRequest
// 因为@Cacheable首次进来肯定命中不了,最终肯定是需要执行一次put操作,这样下次进来就能命中
List<CachePutRequest> cachePutRequests = new ArrayList<>();
if (cacheHit == null) {
collectPutRequests(contexts.get(CacheableOperation.class),
CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);
}
Object cacheValue;
Object returnValue;
// 如果缓存命中了,并且并且没有@CachePut的话,也就直接返回
if (cacheHit != null && !hasCachePut(contexts)) {
cacheValue = cacheHit.get();
returnValue = wrapCacheValue(method, cacheValue);
}
else {
// 有 cachePut 的情况,先执行目标方法再 put 缓存
returnValue = invokeOperation(invoker);
cacheValue = unwrapReturnValue(returnValue);
}
// 封装cacheput对象
collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);
// 真正统一执行 cacheput 的地方
for (CachePutRequest cachePutRequest : cachePutRequests) {
cachePutRequest.apply(cacheValue);
}
// 最后才执行 cacheEvict
processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);
return returnValue;
}
//缓存属性上下文对象
private class CacheOperationContexts {
private final MultiValueMap<Class<? extends CacheOperation>, CacheOperationContext> contexts;
//检查注解是否配置同步执行的开关
private final boolean sync;
public CacheOperationContexts(Collection<? extends CacheOperation> operations, Method method,
Object[] args, Object target, Class<?> targetClass) {
//将当前 method 上所有缓存操作封装到一个map 对象中
this.contexts = new LinkedMultiValueMap<>(operations.size());
for (CacheOperation op : operations) {
this.contexts.add(op.getClass(), getOperationContext(op, method, args, target, targetClass));
}
//同步执行与否取决于这个函数
this.sync = determineSyncFlag(method);
}
public Collection<CacheOperationContext> get(Class<? extends CacheOperation> operationClass) {
Collection<CacheOperationContext> result = this.contexts.get(operationClass);
return (result != null ? result : Collections.emptyList());
}
public boolean isSynchronized() {
return this.sync;
}
//只有@Cacheable有sync属性,所以只需要看CacheableOperation即可
private boolean determineSyncFlag(Method method) {
List<CacheOperationContext> cacheOperationContexts = this.contexts.get(CacheableOperation.class);
if (cacheOperationContexts == null) { // no @Cacheable operation at all
return false;
}
boolean syncEnabled = false;
//只要有一个@Cacheable的sync=true了,那就为true 并且下面还有检查逻辑
for (CacheOperationContext cacheOperationContext : cacheOperationContexts) {
if (((CacheableOperation) cacheOperationContext.getOperation()).isSync()) {
syncEnabled = true;
break;
}
}
// 执行sync=true的检查逻辑
if (syncEnabled) {
// sync=true时候,不能还有其它的缓存操作 也就是说@Cacheable(sync=true)的时候只能单独使用
if (this.contexts.size() > 1) {
throw new IllegalStateException(
"@Cacheable(sync=true) cannot be combined with other cache operations on '" + method + "'");
}
//@Cacheable(sync=true)时,多个@Cacheable也是不允许的
if (cacheOperationContexts.size() > 1) {
throw new IllegalStateException(
"Only one @Cacheable(sync=true) entry is allowed on '" + method + "'");
}
// 拿到唯一的一个@Cacheable
CacheOperationContext cacheOperationContext = cacheOperationContexts.iterator().next();
CacheableOperation operation = (CacheableOperation) cacheOperationContext.getOperation();
//@Cacheable(sync=true)时,cacheName只能使用一个
if (cacheOperationContext.getCaches().size() > 1) {
throw new IllegalStateException(
"@Cacheable(sync=true) only allows a single cache on '" + operation + "'");
}
//sync=true时,unless属性是不支持的,并且是不能写的
if (StringUtils.hasText(operation.getUnless())) {
throw new IllegalStateException(
"@Cacheable(sync=true) does not support unless attribute on '" + operation + "'");
}
return true;
}
return false;
}
}
}
execute 相关的代码上面注解写的很清晰,大家可以跟着多看几遍。这里还有一点没有说,最终的 get Cache 或者 put Cache 的操作在哪里呢?刚才在 execute 方法中有一句代码:
Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class));
这里我们看到是先查缓存中是否有该 Cache,进去看有个 findCaches 方法:
@Nullable
private Cache.ValueWrapper findInCaches(CacheOperationContext context, Object key) {
for (Cache cache : context.getCaches()) {
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;
}
重点关注 doGet 方法,可以看到这个方法是父类 AbstractCacheInvoker 中的,同类里面还有 doPut,doEvict 和 doClear。
小知识
@Cacheable
注解sync=true
的效果多线程环境下存在多个操作使用相同的参数同步调用相同的 key,默认情况下缓存不锁定任何资源所以可能导致多次计算。对于这种情况,
sync
属性可以将底层锁住,使得只有一个线程进行操作,其他线程堵塞直到更新完缓存返回结果。
小结
至此我们把上面说过的内容总结一下:
BeanFactoryCacheOperationSourceAdvisor
配置 Cache 的切面和拦截实现。拦截的对象即解析出来的 CacheOperation 对象。
每一个 CacheOperation 在执行的时候被封装为 CacheOperationContext 对象(一个方法可能被多个注解修饰),最终通过 CacheResolver 解析出缓存对象 Cache。
CacheOperation
封装了@CachePut
、@Cacheable
、@CacheEvict
的属性信息,以便于拦截的时候能直接操作此对象来执行逻辑。
解析注解对应的代码为 CacheOperation 的工作是 CacheAnnotationParser 来完成的。
CacheInterceptor
CacheInterceptor 执行真正的方法执行和 Cache 操作。最终调用其父类提供的四个 do 方法处理 Cache。
以上整体过程为 Spring 启动对相关注解所在类或者方法的拦截和注入,从而实现 Cache 逻辑。限于篇幅本篇暂讨论这些内容,下一篇我们来看 Spring 如何实现对多缓存底层方案的支持(本地 Cache,Redis,Guava Cache,Caffeine Cache)。