spring缓存总结

SpringCache有关中文文档(推荐阅读)

启动缓存支持

spring对缓存的支持有两种方式:

  1. 注解驱动的缓存
  2. XML声明的缓存
注解驱动的缓存

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基本上一模一样,好奇的可以自己去看源码。

小结
  1. @Cacheable通过使用缓存并跳过方法执行,@CachePut强制执行方法并进行缓存更新,对同一个方法同时使用 @CachePut 和 @Cacheable 注解是不推荐的。
  2. 条件化缓存支持使用condition或者unless。condition表达式计算为fasle,是禁用缓存的;unless表达式计算为true,将不会加入缓存,但方法调用的时候,仍然会去缓存中找。

另外这两种方式都支持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内置了一些缓存管理器实现,我们来了解下。

  1. EhCacheCacheManager
使用EhCache缓存
@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;
    }
}
  1. SimpleCacheManager:需要自己设置缓存来源,可以配合ConcurrentMapCache来使用。
  @Bean
  public CacheManager cacheManager() {
      SimpleCacheManager cacheManager = new SimpleCacheManager();
      // 需要自己设置缓存来源
      cacheManager.setCaches(Collections.singletonList(new ConcurrentMapCache("test")));
      return cacheManager;
  }
  1. NoOpCacheManager:一个基本的,无操作的CacheManager实现,适合禁用缓存,通常用于在没有实际备份存储的情况下备份缓存。去看下 NoOpCache就啥都明白了。
  2. ConcurrentMapCacheManager:动态设置ConcurrentMapCache作为缓存,默认不存储,可以接收空值,也可以像SimpleCacheManager那样用,不过不用显示构造ConcurrentMapCache,直接设置缓存名称即可,说了这么多我们直接看下源码。
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);
	}
}
  1. TransactionAwareCacheManagerProxy:利用代理模式使持有的缓存管理器具有事务的相关特性。
	// 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);
		}
	}
  1. CompositeCacheManager:组合式缓存管理器,可以设置多个。
	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;
	}
  1. RedisCacheManager(来自于Spring Data Reids项目):使用Redis作为缓存
// 我使用的springBoot2.3.4
@Configuration
@EnableCaching
public class CacheConfig {
    @Bean
    public RedisCacheManager redisCacheManager(RedisConnectionFactory connectionFactory){
        //这里可以进行一些自定义设置,比如超时时间,序列化器..这里就不搬运代码了
        ...
        return RedisCacheManager.builder (connectionFactory).build ();
    }
}
  1. CaffeineCacheManager:使用CaffeineCache作为缓存,我看了下有关Caffeine的介绍,它是使用Java8对Guava缓存的重写版本,在Spring Boot 2.0中将取代,基于LRU算法实现,支持多种缓存过期策略。至于更详细的大家自行搜索吧。
小贴士

我们自己配置缓存管理的时候,spring提供了一个便利的类CachingConfigurerSupport给我们,我们只需要继承覆盖自己感兴趣的部分即可。方法上面需要添加@Bean注解。

springBoot自动配置之CacheAutoConfiguration
了解CacheAutoConfiguration的配置
  • CacheManagerCustomizers
	@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");
	}
  • @Import({ CacheConfigurationImportSelector.class, CacheManagerEntityManagerFactoryDependsOnPostProcessor.class })
	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。

你可能感兴趣的:(spring,缓存,spring,5,java,spring,boot)