阅读本文需要对Spring和Redis比较熟悉。
Spring Framework 提供了Cache Abstraction对缓存层进行了抽象封装,通过几个annotation可以透明给您的应用增加缓存支持,而不用去关心底层缓存具体由谁实现。目前支持的缓存有java.util.concurrent.ConcurrentMap
,Ehcache 2.x,Redis等。
一般我们使用最常用的Redis做为缓存实现(Spring Data Redis),
- 需要引入的starter:
spring-boot-starter-data-redis
,spring-boot-starter-cache
; - 自动配置生成的Beans:
RedisConnectionFactory
,StringRedisTemplate
,RedisTemplate
,RedisCacheManager
,自动配置的Bean可以直接注入我们的代码中使用;
I. 配置
application.properties
# REDIS (RedisProperties)
spring.redis.host=localhost # Redis server host.
spring.redis.port=6379 # Redis server port.
spring.redis.password= # Login password of the redis server.
具体对Redis cluster或者Sentinel的配置可以参考这里
开启缓存支持
@SpringBootApplication
@EnableCaching//开启caching
public class NewsWebServer {
//省略内容
}
定制RedisTemplate
自动配置的RedisTemplate
并不能满足大部分项目的需求,比如我们基本都需要设置特定的Serializer
(RedisTemplate默认会使用JdkSerializationRedisSerializer
)。
Redis底层中存储的数据只是字节。虽然Redis本身支持各种类型(List, Hash等),但在大多数情况下,这些指的是数据的存储方式,而不是它所代表的内容(内容都是byte)。用户自己来决定数据如何被转换成String或任何其他对象。用户(自定义)类型和原始数据类型之间的互相转换通过RedisSerializer接口(包org.springframework.data.redis.serializer)来处理,顾名思义,它负责处理序列化/反序列化过程。多个实现可以开箱即用,如:StringRedisSerializer和JdkSerializationRedisSerialize。Jackson2JsonRedisSerializer或GenericJackson2JsonRedisSerializer来处理JSON格式的数据。请注意,存储格式不仅限于value 它可以用于key,Hash的key和value。
声明自己的RedisTemplate
覆盖掉自动配置的Bean:
//通用的RedisTemplate
@Bean
public RedisTemplate redisTemplate(JedisConnectionFactory jedisConnectionFactory) {
RedisTemplate template = new RedisTemplate<>();
template.setConnectionFactory(jedisConnectionFactory);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
//template.setHashKeySerializer(template.getKeySerializer());
//template.setHashValueSerializer(template.getValueSerializer());
return template;
}
这里我们使用GenericJackson2JsonRedisSerializer
而不是Jackson2JsonRedisSerializer
,后者的问题是你需要为每一个需要序列化进Redis的类指定一个Jackson2JsonRedisSerializer
因为其构造函数中需要指定一个类型来做反序列化:
redis.setValueSerializer(new Jackson2JsonRedisSerializer(Product.class));
如果我们应用中有大量对象需要缓存,这显然是不合适的,而前者直接把类型信息序列化到了JSON格式中,让一个实例可以操作多个对象的反序列化。
定制RedisCacheManager
有时候Spring Boot自动给我们配置的RedisCacheManager
也不能满足我们应用的需求,我看到很多用法都直接声明了一个自己的RedisCacheManager,其实使用CacheManagerCustomizer
可以对自动配置的RedisCacheManager进行定制化:
@Bean
public CacheManagerCustomizer cacheManagerCustomizer() {
return new CacheManagerCustomizer() {
@Override
public void customize(RedisCacheManager cacheManager) {
cacheManager.setUsePrefix(true); //事实上这是Spring Boot的默认设置,为了避免key冲突
Map expires = new HashMap<>();
expires.put("myLittleCache", 12L*60*60); // 设置过期时间 key is cache-name
expires.put("myBiggerCache", 24L*60*60);
cacheManager.setExpires(expires); // expire per cache
cacheManager.setDefaultExpiration(24*60*60);// 默认过期时间:24 hours
}
};
}
II. 使用
缓存Key的生成
我们都知道Redis是一个key-value的存储系统,无论我们想要缓存什么值,都需要制定一个key。
@Cacheable(cacheNames = "user")
public User findById(long id) {
return userMapper.findById(id);
}
上面的代码中,findById
方法返回的对象会被缓存起来,key由默认的org.springframework.cache.interceptor.SimpleKeyGenerator
生成,生成策略是根据被标注方法的参数生成一个SimpleKey
对象,然后由RedisTemplate
中定义的KeySerializer序列化后作为key(注意StringRedisSerializer
只能序列化String类型,对SimpleKey
对象无能为力,你只能定义其他Serializer)。
不过大多数情况下我们都会采用自己的key生成方案,方式有两种:
1.实现自己的KeyGenerator;
@Configuration
@EnableCaching
public class CacheConfig extends CachingConfigurerSupport {
@Bean
public KeyGenerator customKeyGenerator() {
return new KeyGenerator() {
@Override
public Object generate(Object o, Method method, Object... objects) {
StringBuilder sb = new StringBuilder();
sb.append(o.getClass().getName());
sb.append(method.getName());
for (Object obj : objects) {
sb.append(obj.toString());
}
return sb.toString();
}
};
}
}
2.在@Cacheable
标注中直接声明key:
@Cacheable(cacheNames = "user", key="#id.toString()") ❶
public User findById(long id) {
return userMapper.findById(id);
}
@Cacheable(cacheNames = "user", key="'admin'") ❷
public User findAdmin() {
return userMapper.findAdminUser();
}
@Cacheable(cacheNames = "user", key="#userId + ':address'") ❸
public List findUserAddress(long userId) {
return userMapper.findUserAddress(userId);
}
key的声明形式支持SpEL。
❶ 最终生成的Redis key为:user:100234
,user部分是因为cacheManager.setUsePrefix(true)
,cacheName会被添加到key作为前缀避免引起key的冲突。之所以#id.toString()
要long型转为String是因为我们设置的KeySerializer为StringRedisSerializer
只能用来序列化String。
❷ 如果被标注方法没有参数,我们可以用一个静态的key值,最终生成的key为user:admin
。
❸ 最终生成的key为user:100234:address
。
这种方式更符合我们以前使用Redis的习惯,所以推荐。
直接使用RedisTemplate
有时候标注不能满足我们的使用场景,我们想要直接使用更底层的RedisTemplate
。
@Service
public class FeedService {
@Resource(name="redisTemplate") ❶
private ZSetOperations feedOp;
public List getFeed(int count, long maxId) {
return new ArrayList<>(feedOp.reverseRangeByScore(FEED_CACHE_KEY, 0, maxId, offset, count));
}
//省略
}
❶ 我们可以直接把RedisTemplate的实例注入为ZSetOperations
、ListOperations
、ValueOperations
等类型(Spring IoC Container帮我们做了转化工作,可以参考org.springframework.data.redis.core.ZSetOperationsEditor
)。