Spring Cache 带你飞(一)

Spring 3.1 版本引入基于 annotation 的 cache 技术,提供了一套抽象的缓存实现方案,通过注解方式使用缓存,基于配置的方式灵活使用不同缓存组件。代码具有相当的灵活性和扩展性,本文基于 Spring 5.x 源码一起分析 Spring Cache 的代码艺术。

开启 Spring Cache#

想让 Spring 提供 Cache 能力很简单,只需要在启动类加上 @EnableCaching 注解即可:

Copy@Configuration
@EnableCaching
public class ServerMain {

}

通过在启动类上添加 EnableCaching 注解将 Cache 相关的组件注入到 Spring 启动中,通过 Proxy 或者 AspectJ 的方式获取 Cache 对应的执行信息。

如果你希望在启动时修改一些 Cache 底层对应的基础管理信息,可以通过在启动类上覆盖 CachingConfigurerSupport 提供的相关方法来实现:

Copy@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 管理方式:

Copy@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 类,我们看看加载了什么信息:

Copypublic 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 类型:

Copy@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 解决要对拦截到的切面做什么。

Copypublic 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,切入的逻辑如下:

Copyabstract 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

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

CacheOperationSource 持有一个 CacheAnnotationParser 列表。 CacheAnnotationParser 只有一个实现类:SpringCacheAnnotationParser

Copypublic 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 存起来。

看到这里我们开始有点眉目,开始知道去哪里找拦截了什么和操作了什么。接下来继续对 CacheOperationCacheInterceptor 进行深入。

CacheOperation

CacheOperationSource 接口中只有一个方法:

Copypublic interface CacheOperationSource {
    default boolean isCandidateClass(Class<?> targetClass) {
        return true;
    }

    @Nullable
    Collection<CacheOperation> getCacheOperations(Method method, @Nullable Class<?> targetClass);
}

该接口中只有一个方法:通过接口和类名获得对应的 CacheOperationCacheOperation 是对缓存操作的抽象封装,它的实现类有 3 个:

Spring Cache 带你飞(一)_第1张图片

三个实现类分别对应着 @CacheEvict@CachePut@Cacheable 注解。

CacheInterceptor#

CacheInterceptor 实现的功能就是对 目标方法的实际拦截操作:

Copypublic 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 看相关代码逻辑:

Copypublic 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 metadataCache

这个 Map 缓存了所有被注解修饰的类或者方法对应的基本属性信息。

CacheOperationExpressionEvaluator evaluator

解析一些 condition、key、unless 等可以写 el 表达式的处理器。

SingletonSupplier keyGenerator

key 生成器默认使用的 SimpleKeyGenerator,注意 SingletonSupplier 是 Spring5.1 的新类,实现了接口java.util.function.Supplier ,主要是对 null 值进行容错。

接着看相关的方法:

Copypublic 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 方法中有一句代码:

CopyCache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class));

这里我们看到是先查缓存中是否有该 Cache,进去看有个 findCaches 方法:

Copy@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)。

你可能感兴趣的:(java,spring,java,mybatis)