官方文档
https://spring.io/guides/gs/caching/
https://docs.spring.io/spring/docs/current/spring-framework-reference/integration.html#cache
https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-caching
https://docs.spring.io/spring-data/redis/docs/current/reference/html/
核心原理
Spring Cache
框架由Cache
和CacheManager
两个组件组成。
Cache
定义缓存操作的接口标准,使用底层缓存(如Ehcache、redis等)实现。
CacheManager
则是对Cache的管理接口,并提供额外的配置服务。
源码解读参考:
SpringCache实现原理及核心业务逻辑(一)
SpringCache实现原理及核心业务逻辑(二)
SpringCache实现原理及核心业务逻辑(三)
Spring Boot默认实现
Cache实现:org.springframework.cache.concurrent.ConcurrentMapCache
CacheManager实现:org.springframework.cache.concurrent.ConcurrentMapCacheManager
自动配置:SimpleCacheConfiguration
@SpringBootApplication
public class Application implements CommandLineRunner{
@Autowired
private CacheManager cacheManager;
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Override
public void run(String... args) {
System.out.println(cacheManager.getClass());
System.out.println(cacheManager.getCache("test").getClass());
}
}
class org.springframework.cache.concurrent.ConcurrentMapCacheManager
class org.springframework.cache.concurrent.ConcurrentMapCache
缓存使用
Spring提供了一套@Cacheable
、@CachePut
、@CacheEvict
注解对缓存进行操作,实际上是使用AOP对CacheManager、Cache进行封装。
在配置类上加上@EnableCaching
注解,Spring Cache才会生效。
这三个注解的使用:
https://www.cnblogs.com/imyijie/p/6518547.html
缓存key支持的EL表达式上下文:
https://blog.csdn.net/m0_37962779/article/details/78747619
自定义缓存数据 key 生成策略:
https://blog.csdn.net/defonds/article/details/48716161
注意和限制:
https://www.ibm.com/developerworks/cn/opensource/os-cn-spring-cache/index.html
Spring Boot集成redis
添加spring-boot-starter-data-redis后,Spring Boot自动识别redis,使用redis的Cache和CacheMager实现来操作缓存。
Cache实现:org.springframework.data.redis.cache.RedisCache
CacheManager实现:org.springframework.data.redis.cache.RedisCacheManager
自动配置:org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration
案例可参考:
https://blog.csdn.net/abombhz/article/details/78123253?locationNum=6&fps=1
https://my.oschina.net/mrfu/blog/1631805
Spring Boot不同版本配置会不太一样,以下都以Spring Boot2.0.1版本为例。
redis属性
完整属性参考:RedisProperties
# REDIS (RedisProperties)
# Redis数据库索引(默认为0)
spring.redis.database=0
# Redis服务器地址
spring.redis.host=192.168.0.58
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.lettuce.pool.max-active=8
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.lettuce.pool.max-wait = PT-0.001S
# 连接池中的最大空闲连接
spring.redis.lettuce.pool.max-idle=8
# 连接池中的最小空闲连接
spring.redis.lettuce.pool.min-idle=0
redis缓存配置
缓存配置类:RedisCacheConfiguration
配置名称 | 说明 | 默认值 | application.properties |
---|---|---|---|
ttl | 全局生存时间 | 0 | spring.cache.redis.timeToLive |
cacheNullValues | 是否缓存空值 | true | spring.cache.redis.cacheNullValues |
usePrefix | 是否使用前缀 | true | spring.cache.redis.useKeyPrefix |
keyPrefix | 前缀表达式 | cacheName + “::” | spring.cache.redis.keyPrefix |
keySerializationPair | 缓存key的序列化器 | StringRedisSerializer | 无 |
valueSerializationPair | 缓存value的序列化器 | JdkSerializationRedisSerializer | 无 |
| 格式转换器 | DefaultFormattingConversionService | 无 |
/**
* 默认缓存配置
*/
public static RedisCacheConfiguration defaultCacheConfig() {
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
registerDefaultConverters(conversionService);
return new RedisCacheConfiguration(Duration.ZERO, true, true, CacheKeyPrefix.simple(),
SerializationPair.fromSerializer(new StringRedisSerializer()),
SerializationPair.fromSerializer(new JdkSerializationRedisSerializer()), conversionService);
}
自定义配置
下面是我项目使用的配置:
@EnableCaching
@Configuration
public class CacheConfig {
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
return RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(defaultCacheConfig())
.withInitialCacheConfigurations(getCacheConfig())
.transactionAware()
.build();
}
/**
* 对每一个缓存单独配置TTL,value序列化器使用Jackson2JsonRedisSerializer
*/
private Map<String,RedisCacheConfiguration> getCacheConfig(){
Map<String,RedisCacheConfiguration> cacheConfigurations = new HashMap<>(16);
for (CacheType cacheType : CacheType.values()) {
RedisCacheConfiguration config = defaultCacheConfig()
.entryTtl(cacheType.getTtl())
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new Jackson2JsonRedisSerializer<>(cacheType.getClz())));
cacheConfigurations.put(cacheType.getCacheName(),config);
}
return cacheConfigurations;
}
/**
* 自定义全局配置
*/
@Bean
RedisCacheConfiguration defaultCacheConfig(){
return RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.of(30, ChronoUnit.MINUTES))
.computePrefixWith(cacheName -> "liyuan-cache:" + cacheName + ":");
}
}
public interface CacheConstant {
String USER_CACHE_NAME_1 = "user-id";
String USER_CACHE_NAME_2 = "user-account";
String ITEM_CACHE_NAME_1 = "item-id";
String ITEM_CACHE_NAME_2 = "item-name";
}
public enum CacheType {
USER_BY_ID(CacheConstant.USER_CACHE_NAME_1,Duration.of(60, ChronoUnit.MINUTES),User.class),
USER_BY_ACCOUNT(CacheConstant.USER_CACHE_NAME_2,Duration.of(60, ChronoUnit.MINUTES),User.class),
ITEM_BY_ID(CacheConstant.ITEM_CACHE_NAME_1,Duration.of(1, ChronoUnit.DAYS),Item.class),
ITEM_BY_NAME(CacheConstant.ITEM_CACHE_NAME_2,Duration.of(1, ChronoUnit.DAYS),Item.class);
CacheType(String cacheName, Duration ttl, Class<?> clz) {
this.cacheName = cacheName;
this.ttl = ttl;
this.clz = clz;
}
/**
* 缓存名称
*/
private String cacheName;
/**
* 存活时间
*/
private Duration ttl;
/**
* 缓存对象
*/
private Class<?> clz;
public String getCacheName() {
return cacheName;
}
public void setCacheName(String cacheName) {
this.cacheName = cacheName;
}
public Duration getTtl() {
return ttl;
}
public void setTtl(Duration ttl) {
this.ttl = ttl;
}
public Class<?> getClz() {
return clz;
}
public void setClz(Class<?> clz) {
this.clz = clz;
}
}
@RunWith(SpringRunner.class)
@SpringBootTest
public class CacheTest {
@Autowired
private RedisCacheManager redisCacheManager;
@Test
public void test() throws Exception {
Cache cache = redisCacheManager.getCache(CacheConstant.USER_CACHE_NAME_1);
cache.evict(1);
Cache.ValueWrapper valueWrapper = cache.get(1);
Assert.assertNull(valueWrapper);
cache.put(1,new User(1,"liyuan"));
valueWrapper = cache.get(1);
Assert.assertNotNull(valueWrapper);
User user = (User)valueWrapper.get();
Assert.assertEquals("liyuan",user.getName());
}
}