SpringCache有关中文文档(推荐阅读)
spring对缓存的支持有两种方式:
JAVA配置
@Configuration
@EnableCaching
public class CacheConfig{
//配置缓存管理器 ...
}
XML配置
<cache:annoatation-driven cache-manager="cacheManager"/>
<bean id="cacheManager" class="">bean>
上面两种本质上工作方式是相同的,都是通过AOP实现了对缓存的管理。我们来了解一下。
@EnableCaching注解默认代理模式将会引入ProxyCachingConfiguration配置类以及自动代理注册类AutoProxyRegistrar。让我们来看一下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;
}
}
BeanFactoryCacheOperationSourceAdvisor这个切面很简单,我们只要关注下配置的切入点CacheOperationSourcePointcut就可以了。其中matches方法表示切入需要满足的条件。
public boolean matches(Method method, Class<?> targetClass) {
CacheOperationSource cas = getCacheOperationSource();
return (cas != null && !CollectionUtils.isEmpty(cas.getCacheOperations(method, targetClass)));
}
继续跟踪,我们来到AbstractFallbackCacheOperationSource#getCacheOperations方法
// 获取方法上的缓存注解并缓存
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 {
// 获取缓存操作,这里是主要逻辑
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;
}
}
获取“缓存操作”最终将调用SpringCacheAnnotationParser去解析这四个注解,包括@Cacheable,@CacheEvict,@CachePut,@Caching。每个注解我们都可以配置一些属性,如果我们想要给某个类的一组缓存操作添加相同的属性,可以在类上使用@CacheConfig统一配置。@CacheConfig是在哪里起作用的呢?可以查看SpringCacheAnnotationParser内部类DefaultCacheConfig#applyDefault方法。
上面介绍的这部分主要是切入点方法匹配后,解析缓存注解并缓存的操作,而根据缓存注解对方法返回值进行缓存或剔除缓存的操作则是在CacheInterceptor里。CacheInterceptor继承自CacheAspectSupport,方法拦截的逻辑就是下面这段代码。
private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {
// 处理缓存同步,如果配置了sync属性
if (contexts.isSynchronized()) {
CacheOperationContext context = contexts.get(CacheableOperation.class).iterator().next();
// condition条件返回true
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) {
ReflectionUtils.rethrowRuntimeException(ex.getCause());
}
}else {
// condition返回false后,将不会加入缓存
return invokeOperation(invoker);
}
}
// 方法调用前 清除缓存条目
processCacheEvicts(contexts.get(CacheEvictOperation.class), true,
CacheOperationExpressionEvaluator.NO_RESULT);
// 检查是否有一个与条件匹配的缓存项
Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class));
List<CachePutRequest> cachePutRequests = new ArrayList<>();
if (cacheHit == null) {
// 如果没有找到缓存项,但是返回值是需要缓存的,这里收集下
collectPutRequests(contexts.get(CacheableOperation.class),
CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);
}
Object cacheValue;
Object returnValue;
if (cacheHit != null && !hasCachePut(contexts)) {
// 缓存命中并且没有指定满足条件的@CachePut
cacheValue = cacheHit.get();
returnValue = wrapCacheValue(method, cacheValue);
}else {
// 如果没有命中缓存或缓存命中但指定了满足条件的@CachePut,则直接调用该方法
returnValue = invokeOperation(invoker);
cacheValue = unwrapReturnValue(returnValue);
}
// 收集任何显式的@CachePut
collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);
// 这里处理缓存未命中或@CachePut指定的方法
for (CachePutRequest cachePutRequest : cachePutRequests) {
// 这里将会对结果进行缓存,如果指定了unless条件,unless返回false才会进行缓存
cachePutRequest.apply(cacheValue);
}
// 方法执行后清除缓存条目
processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);
return returnValue;
}
上面代码中参数CacheOperationContexts是根据缓存操作构建的上下文,如果我们显式指定了例如KeyGenerator,CacheManager的属性,将会从容器中查找对应的bean。
而基于XML方式实现的注解驱动则是通过AnnotationDrivenCacheBeanDefinitionParser解析XML,之后的逻辑和使用@EnableCaching基本上一模一样,好奇的可以自己去看源码。
另外这两种方式都支持JCache,只要我们引入了对应的jar包就可以了。
static {
ClassLoader classLoader = CachingConfigurationSelector.class.getClassLoader();
// javax.cache.Cache的jar
jsr107Present = ClassUtils.isPresent("javax.cache.Cache", classLoader);
// spring-contxt-support的jar
jcacheImplPresent = ClassUtils.isPresent(PROXY_JCACHE_CONFIGURATION_CLASS, classLoader);
}
JCache虽然是官方制定的规范,但是它出身晚,支持的缓存提供商并没有SpringCache多,所以对于我个人而言,我是倾向于在项目中使用SpringCache的,这里我就不去详细介绍JCache了。
前面配置的时候不管是JAVA还是XML配置,关于缓存管理器这块我都是注释了,并没有配置具体哪个缓存管理器,而这个得看项目需要。Spring内置了一些缓存管理器实现,我们来了解下。
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public EhCacheCacheManager cacheManager(CacheManager cm){
EhCacheCacheManager cacheManager = new EhCacheCacheManager (cm);
//如果事务没有激活,就和正常的Cache功能一样,但激活后,put/evict/clear缓存操作将在事务成功提交后执行
cacheManager.setTransactionAware (true);
return cacheManager;
}
@Bean
public EhCacheManagerFactoryBean ehcache(){
EhCacheManagerFactoryBean factoryBean = new EhCacheManagerFactoryBean ();
factoryBean.setConfigLocation (new ClassPathResource ("ehcache.xml"));
return factoryBean;
}
}
@Bean
public CacheManager cacheManager() {
SimpleCacheManager cacheManager = new SimpleCacheManager();
// 需要自己设置缓存来源
cacheManager.setCaches(Collections.singletonList(new ConcurrentMapCache("test")));
return cacheManager;
}
public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderAware {
// 使用ConcurrentHashMap作为缓存介质,它是不支持空值的,但此缓存管理器却提供了允许值为空的属性
// 实际上当值为空的时候,如果允许为空,存储的将是new Object()。
private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<>(16);
// 是否动态设置
private boolean dynamic = true;
// 允许值为空
private boolean allowNullValues = true;
// 是否存储
private boolean storeByValue = false;
// 序列化委托,默认初始化一个Serializer以及Deserializer
private SerializationDelegate serialization;
//当设置新的属性时会重置缓存
private void recreateCaches() {
for (Map.Entry<String, Cache> entry : this.cacheMap.entrySet()) {
entry.setValue(createConcurrentMapCache(entry.getKey()));
}
}
// 支持静态模式设置
public void setCacheNames(@Nullable Collection<String> cacheNames) {
if (cacheNames != null) {
for (String name : cacheNames) {
this.cacheMap.put(name, createConcurrentMapCache(name));
}
this.dynamic = false;
}else {
this.dynamic = true;
}
}
protected Cache createConcurrentMapCache(String name) {
// 是否存储
SerializationDelegate actualSerialization = (isStoreByValue() ? this.serialization : null);
return new ConcurrentMapCache(name, new ConcurrentHashMap<>(256), isAllowNullValues(), actualSerialization);
}
}
// TransactionAwareCacheManagerProxy#getCache
public Cache getCache(String name) {
Assert.state(this.targetCacheManager != null, "No target CacheManager set");
// 从代理的缓存管理器中获取缓存
Cache targetCache = this.targetCacheManager.getCache(name);
// 装饰目标缓存
return (targetCache != null ? new TransactionAwareCacheDecorator(targetCache) : null);
}
// 我们看下TransactionAwareCacheDecorator#put
public void put(final Object key, @Nullable final Object value) {
// 缓存激活
if (TransactionSynchronizationManager.isSynchronizationActive()) {
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override
public void afterCommit() {
// 事务成功提交后,处理缓存操作
TransactionAwareCacheDecorator.this.targetCache.put(key, value);
}
});
}else {
// 事务未激活,保持和原来操作一样
this.targetCache.put(key, value);
}
}
public Cache getCache(String name) {
// 循环遍历cacheManagers
for (CacheManager cacheManager : this.cacheManagers) {
// 可以通过设置fallbackToNoOpCache为true,添加到cacheManagers末尾,这样缓存无论什么
// 情况都不会返回null
Cache cache = cacheManager.getCache(name);
if (cache != null) {
return cache;
}
}
return null;
}
// 我使用的springBoot2.3.4
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public RedisCacheManager redisCacheManager(RedisConnectionFactory connectionFactory){
//这里可以进行一些自定义设置,比如超时时间,序列化器..这里就不搬运代码了
...
return RedisCacheManager.builder (connectionFactory).build ();
}
}
我们自己配置缓存管理的时候,spring提供了一个便利的类CachingConfigurerSupport给我们,我们只需要继承覆盖自己感兴趣的部分即可。方法上面需要添加@Bean注解。
@Bean
@ConditionalOnMissingBean
public CacheManagerCustomizers cacheManagerCustomizers(ObjectProvider<CacheManagerCustomizer<?>> customizers) {
return new CacheManagerCustomizers(customizers.orderedStream().collect(Collectors.toList()));
}
这是缓存管理器自定义设置,我们来看下CacheManagerCustomizer接口,可以发现它将CacheManager暴露出来了,那我们就可以对这个CacheManager进行相关的配置了。
public interface CacheManagerCustomizer<T extends CacheManager> {
/**
* 自定义缓存管理器
*/
void customize(T cacheManager);
}
demo
static class CacheNamesCacheManagerCustomizer implements CacheManagerCustomizer<ConcurrentMapCacheManager> {
@Override
public void customize(ConcurrentMapCacheManager cacheManager) {
cacheManager.setCacheNames(Arrays.asList("one", "two"));
}
}
@Test
void customizeSimpleCacheManager() {
CacheManagerCustomizers customizers = new CacheManagerCustomizers(
Collections.singletonList(new CacheNamesCacheManagerCustomizer()));
ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
customizers.customize(cacheManager);
assertThat(cacheManager.getCacheNames()).containsOnly("one", "two");
}
static class CacheConfigurationImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
// 枚举类CacheType包含GENERIC,JCACHE,EHCACHE,HAZELCAST,INFINISPAN,COUCHBASE
// REDIS,CAFFEINE,SIMPLE,NONE
CacheType[] types = CacheType.values();
String[] imports = new String[types.length];
for (int i = 0; i < types.length; i++) {
// 从缓存类型的相关配置映射表中获取 对应的配置文件的类名
imports[i] = CacheConfigurations.getConfigurationClass(types[i]);
}
// 那么这个imports不是把所有的缓存配置都导入了吗?我们仔细看一下每个缓存配置类
// 可以发现每个类都有添加条件注解@Conditional,而实际上只有一个配置类会被导入,
// 默认为SimpleCacheConfiguration
return imports;
}
}
针对为什么默认的缓存配置类是SimpleCacheConfiguration,我们来看下
CacheConfigurations:
// 静态加载的缓存类型对应缓存配置映射表,注意EnumMap能够保证遍历时顺序与存放顺序一致
static {
Map<CacheType, Class<?>> mappings = new EnumMap<>(CacheType.class);
mappings.put(CacheType.GENERIC, GenericCacheConfiguration.class);
mappings.put(CacheType.EHCACHE, EhCacheCacheConfiguration.class);
mappings.put(CacheType.HAZELCAST, HazelcastCacheConfiguration.class);
mappings.put(CacheType.INFINISPAN, InfinispanCacheConfiguration.class);
mappings.put(CacheType.JCACHE, JCacheCacheConfiguration.class);
mappings.put(CacheType.COUCHBASE, CouchbaseCacheConfiguration.class);
mappings.put(CacheType.REDIS, RedisCacheConfiguration.class);
mappings.put(CacheType.CAFFEINE, CaffeineCacheConfiguration.class);
// 上面的缓存配置类 每一个都设置了对应的条件,条件不满足时不会加载
// SimpleCacheConfiguration设置的条件:1.没有配置CacheManager 2.满足CacheCondition
mappings.put(CacheType.SIMPLE, SimpleCacheConfiguration.class);
// SimpleCacheConfiguration加载后,将会配置CacheManager,那么NoOpCacheConfiguration将不
// 再满足条件
mappings.put(CacheType.NONE, NoOpCacheConfiguration.class);
MAPPINGS = Collections.unmodifiableMap(mappings);
}
我们接着来看下CacheCondition#getMatchOutcome方法
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
String sourceClass = "";
if (metadata instanceof ClassMetadata) {
sourceClass = ((ClassMetadata) metadata).getClassName();
}
ConditionMessage.Builder message = ConditionMessage.forCondition("Cache", sourceClass);
Environment environment = context.getEnvironment();
try {
// spring.cache.type有没有配置缓存类型
BindResult<CacheType> specified = Binder.get(environment).bind("spring.cache.type", CacheType.class);
if (!specified.isBound()) {
// 这块直接返回就说明了没有配置缓存类型,将使用SimpleCacheConfiguration
return ConditionOutcome.match(message.because("automatic cache type"));
}
// 获取缓存配置类对应的缓存类型
CacheType required = CacheConfigurations.getType(((AnnotationMetadata) metadata).getClassName());
// 缓存类型与配置类型匹配
if (specified.get() == required) {
// 则加载对应的缓存配置类
return ConditionOutcome.match(message.because(specified.get() + " cache type"));
}
}catch (BindException ex) {
}
return ConditionOutcome.noMatch(message.because("unknown cache type"));
}
另外我们查看每一个缓存配置类,可以发现构建CacheManager的时候,会显示的调用CacheManagerCustomizers#customize方法,那么我们如果想要进行自定义配置的话,只需要注入CacheManagerCustomizer的bean即可。
springBoot因为CacheAutoConfiguration的存在,如果我们只需要配置单一的缓存管理器,只要在配置文件中配置spring.cache.type就行了,不知道对应的缓存类型名称是啥的话,就去CacheType中去找。配置了这个之后,只是帮我们配置了缓存管理器而已,要想缓存起作用,需要配合@EnableCaching。