Spring Cache
什么是Spring Cache?
Spring Cache 是Spring 提供的一整套的缓存解决方案,它不是具体的缓存实现,它只提供一整套的接口和代码规范、配置、注解等,用于整合各种缓存方案,比如Caffeine、Guava Cache、Ehcache
Spring Cache并不是缓存的实现,而是缓存使用的一种方式,其基于注解和Spring高级特性提供缓存读写以及失效刷新等各种能力
Spring Cache实现:
在 Spring 3.1 中引入了多 Cache 的支持,在 spring-context 包中定义了org.springframework.cache.Cache 和 org.springframework.cache.CacheManager 两个接口来统一不同的缓存技术。
Cache 接口包含缓存的常用操作:增加、删除、读取等。CacheManager 是 Spring 各种缓存的抽象接口。
Spring 支持的常用 CacheManager 如下:
CaffeineCacheManager | 使用 Caffeine 作为缓存 |
CompositeCacheManager | 用于组合 CacheManager,可以从多个 CacheManager 中轮询得到相应的缓存 |
ConcurrentMapCacheManager |
默认,使用 java.util.ConcurrentHashMap 来实现缓存 private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<>(16); |
EhCacheCacheManager | |
JCacheCacheManager | |
NoOpCacheManager | 仅测试用,不会实际存储缓存 |
RedisCacheManager | 使用Redis作为缓存 |
SimpleCacheManager |
使用简单的 Collection 来缓存 private Collection<? extends Cache> caches = Collections.emptySet(); |
默认的缓存实现是ConcurrentMapCacheManager
除此之外,抽象的 CacheManager 既能集成基于本地内存的单体应用,也能集成 EhCache、Redis 等缓存服务器
Spring Cache使用
1、Caffeine Cache
1、添加Spring Cache依赖
<!--spring cache-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
2、添加缓存服务依赖,如Caffeine
<!--Caffeine-->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>3.1.2</version> <!-- spring-boot-dependencies在dependencyManagement里指定了Caffeine依赖的版本,此处可以不用指定 -->
</dependency>
3、配置缓存管理器
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.TimeUnit;
/**
* SpringCache配置
*
* @author yangyongjie
*/
@EnableCaching
@Configuration
public class SpringCacheConfig {
/**
* 配置缓存管理器
*
* @return 缓存管理器
*/
@Bean("caffeineCacheManager")
public CacheManager caffeineCacheManager() {
CaffeineCacheManager caffeineCacheManager = new CaffeineCacheManager();
caffeineCacheManager.setCaffeine(Caffeine.newBuilder()
// 设置最后一次写入或访问后经过固定时间过期
.expireAfterAccess(10, TimeUnit.MINUTES)
// 初始的缓存空间大小
.initialCapacity(200)
// 缓存的最大条数
.maximumSize(1000));
return caffeineCacheManager;
}
}
4、开启Spring Cache
启动类或者配置类上添加注解 @EnableCaching
5、使用注解激活缓存,在业务服务类或者在mapper上使用注解:
@CacheConfig(cacheNames = "caffeineCacheManager")
public interface CcContentLevelMapper {
/**
* 按id查询
*
* @param id
* @return
*/
@Cacheable(key = "#id")
CcContentLevel selectById(Long id);
/**
* 按cpId与channelId查询
*
* @param cpId
* @param channelId
* @return
*/
@Cacheable(key = "#cpId+'_'+#channelId")
CcContentLevel selectByCpIdAndChannelId(Integer cpId, Integer channelId);
}
Spring Cache注解说明:
注解 |
功能 |
用法 |
说明 |
@Cacheable: |
触发缓存写入 |
用于查询方法 |
触发缓存读取操作,用于查询方法上,如果缓存中找到则直接取出缓存并返回,否则执行目标方法并将结果缓存 |
@CachePut: | 在不干扰方法执行的情况下更新缓存 | 用在新增/更新方法 | 触发缓存写入的方法上,与 Cacheable 相比,该注解的方法始终都会被执行,并且使用方法返回的结果去写入缓存 |
@CacheEvict | 触发缓存删除 | 更新或者删除的方法 | 触发缓存失效,删除缓存项或者清空缓存 |
@Caching | 将多个缓存操作重新分组以应用于方法 | ||
@CacheConfig | 在类级别共享一些常见的缓存相关设置 | 类上 | 当我们需要缓存的地方越来越多,你可以使用@CacheConfig(cacheNames = {"cacheName"})注解在 class 之上来统一指定value的值,这时可省略value,如果你在你的方法依旧写上了value,那么依然以方法的value值为准 |
Spring Cache源码解读:
1、CachingConfigurationSelector
@Import(CachingConfigurationSelector.class)
public @interface EnableCaching {...}
EnableCaching注解导入了CachingConfigurationSelector
CachingConfigurationSelector注册了两个Bean
AutoProxyRegistrar.class
ProxyCachingConfiguration.class
2、ProxyCachingConfiguration 注入了三个bean,BeanFactoryCacheOperationSourceAdvisor、CacheOperationSource、CacheInterceptor,重点关注CacheInterceptor
3、CacheInterceptor:对于@Cacheable,@CachePut,@CacheEvict等注解做一个拦截aop切面操作
Object target = invocation.getThis();通过代理创建并获取了这个对象
Method method = invocation.getMethod();获取了需要进行切面的方法
执行execute(aopAllianceInvoker, target, method, invocation.getArguments());
4、CacheAspectSupport
execute方法进入到了CacheAspectSupport中
// Check if we have a cached item matching the conditions 检查是否有匹配的缓存
Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class));
private Cache.ValueWrapper findCachedItem(Collection<CacheOperationContext> contexts) {
Object result = CacheOperationExpressionEvaluator.NO_RESULT;
for (CacheOperationContext context : contexts) {
if (isConditionPassing(context, result)) {
Object key = generateKey(context, result);
Cache.ValueWrapper cached = findInCaches(context, key);
if (cached != null) {
return cached;
}
else {
if (logger.isTraceEnabled()) {
logger.trace("No cache entry for key '" + key + "' in cache(s) " + context.getCacheNames());
}
}
}
}
return null;
}
如果命中缓存,则返回缓存,否则调用查询获取结果,然后缓存起来
if (cacheHit != null && !hasCachePut(contexts)) {
// If there are no put requests, just use the cache hit
cacheValue = cacheHit.get();
returnValue = wrapCacheValue(method, cacheValue);
}
else {
// Invoke the method if we don't have a cache hit
returnValue = invokeOperation(invoker);
cacheValue = unwrapReturnValue(returnValue);
}
总结:Spring Cache会根据 被代理的类+方法+返回类型+id寻找是否存在cache,存在则缓存,否则执行数据库查询操作
2、Redis Cache
<!-- Spring Cache的引入-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!-- redis依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
缓存管理器
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import java.time.Duration;
/**
* SpringCache配置
*
* @author yangyongjie
*/
@EnableCaching
@Configuration
public class SpringCacheConfig {
@Bean
@Primary // 加入这个注解表示默认使用
public RedisCacheManager redisCacheManager30M(RedisConnectionFactory connectionFactory) {
RedisCacheConfiguration config = initRedisCacheConfig(30L);
return RedisCacheManager.builder(connectionFactory).cacheDefaults(config).build();
}
/**
* 1小时过期的Redis缓存管理器
*
* @param connectionFactory Redis连接工厂
* @return Redis缓存管理器
*/
@Bean
public RedisCacheManager redisCacheManager1H(RedisConnectionFactory connectionFactory) {
RedisCacheConfiguration config = initRedisCacheConfig(60L);
return RedisCacheManager.builder(connectionFactory).cacheDefaults(config).build();
}
/**
* 初始化Redis缓存配置
*
* @param minute 分钟
* @return Redis缓存配置
*/
private RedisCacheConfiguration initRedisCacheConfig(Long minute) {
return RedisCacheConfiguration.defaultCacheConfig()
// 设置缓存的默认过期时间(min)
.entryTtl(Duration.ofMinutes(minute))
// 不缓存空值
.disableCachingNullValues()
// 覆盖默认的构造key,否则会多出一个冒号 原规则:cacheName::key 现规则:cacheName:key
.computePrefixWith(name -> name + ":");
}
}
在CacheManager中可以设置usePrefix的值,如果为true,那么最后操作redis的key=value::(key或者keyGenerator生成的key);如果为false,那么就是原始key
END.