閱讀本文需要對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<String, Object> redisTemplate(JedisConnectionFactory jedisConnectionFactory) {
RedisTemplate<String, Object> 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>(Product.class));複製程式碼
如果我們應用中有大量物件需要快取,這顯然是不合適的,而前者直接把型別資訊序列化到了JSON格式中,讓一個例項可以操作多個物件的反序列化。
定製RedisCacheManager
有時候Spring Boot自動給我們配置的RedisCacheManager
也不能滿足我們應用的需求,我看到很多用法都直接宣告瞭一個自己的RedisCacheManager,其實使用CacheManagerCustomizer
可以對自動配置的RedisCacheManager進行定製化:
@Bean
public CacheManagerCustomizer<RedisCacheManager> cacheManagerCustomizer() {
return new CacheManagerCustomizer<RedisCacheManager>() {
@Override
public void customize(RedisCacheManager cacheManager) {
cacheManager.setUsePrefix(true); //事實上這是Spring Boot的預設設定,為了避免key衝突
Map<String, Long> 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<Address> 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<String, Feed> feedOp;
public List<Feed> 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
)。
除了當快取,Redis還能幹啥
org.springframework.data.redis.support
包中提供了一些以Redis作為後端儲存的元件,包括原子計數器和Java Collections的一些實現類。
@Service
public class ActivityService {
RedisAtomicInteger counter;
public ActivityService(RedisConnectionFactory connectionFactory) {
counter = new RedisAtomicInteger(counterKey,
connectionFactory, 1);
}
}複製程式碼
以上程式碼建立了一個分散式的原子計數器。
III. 參考
#coding/spring