從零搭建Spring Boot腳手架:整合Redis作為緩存
前言
今天我們會(huì)把緩存也集成進(jìn)來(lái)。緩存是一個(gè)系統(tǒng)應(yīng)用必備的一種功能,除了在減輕數(shù)據(jù)庫(kù)的壓力之外。還在存儲(chǔ)一些短時(shí)效的數(shù)據(jù)場(chǎng)景中發(fā)揮著重大作用,比如存儲(chǔ)用戶Token、短信驗(yàn)證碼等等,目前緩存的選型還是比較多的,EHCACHE、HAZELCAST、CAFFEINE、COUCHBASE以及本文要整合的REDIS。接下來(lái)我們將會(huì)在kono腳手架項(xiàng)目中集成Spring Cache以及Redis。
Gitee: https://gitee.com/felord/kono day05 分支
GitHub: https://github.com/NotFound403/kono day05 分支
2. 整合目標(biāo)
使項(xiàng)目具有緩存功能,同時(shí)將默認(rèn)的JDK序列化修改為Jackson序列化以存儲(chǔ)一些對(duì)象,同時(shí)實(shí)現(xiàn)一些特定的個(gè)性化的緩存空間以滿足不同場(chǎng)景下的不同緩存TTL時(shí)間需求。
3. 依賴(lài)集成
目前只需要引入下面的依賴(lài)即可:
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-data-redis</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-cache</artifactId>
- </dependency>
- <dependency>
- <groupId>org.apache.commons</groupId>
- <artifactId>commons-pool2</artifactId>
- </dependency>
默認(rèn)情況下spring-data-redis使用高性能的lettuce客戶端實(shí)現(xiàn),當(dāng)然你可以替換為老舊的jedis。
4. 緩存及 Redis 配置
緩存以及Redis相關(guān)的配置項(xiàng)分別為spring.cache和spring.redis開(kāi)頭的配置,這里比較簡(jiǎn)單的配置為:
- spring:
- redis:
- host: localhost
- port: 6379
- cache:
- # type: REDIS
- redis:
- # 全局過(guò)期時(shí)間
- time-to-live: 120
5. RedisTemplate 個(gè)性化
默認(rèn)情況下會(huì)有兩個(gè)模板類(lèi)被注入Spring IoC供我們使用,需要個(gè)性化配置來(lái)滿足實(shí)際的開(kāi)發(fā)。
一個(gè)是RedisTemplate<Object, Object>,主要用于對(duì)象緩存,其默認(rèn)使用JDK序列化,我們需要更改其序列化方式解決一些問(wèn)題,比如Java 8日期問(wèn)題、JSON序列化問(wèn)題。需要我們重寫(xiě)一下。
- /**
- * Redis的一些自定義配置.
- *
- * @author felord.cn
- * @since 2020 /8/17 20:39
- */
- @ConditionalOnClass(ObjectMapper.class)
- @Configuration(proxyBeanMethods = false)
- public class RedisConfiguration {
- /**
- * Redis template redis template.
- *
- * @param redisConnectionFactory the redis connection factory
- * @return the redis template
- */
- @Bean("redisTemplate")
- public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
- RedisTemplate<Object, Object> template = new RedisTemplate<>();
- template.setConnectionFactory(redisConnectionFactory);
- // 使用Jackson2JsonRedisSerialize 替換默認(rèn)序列化
- Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = initJacksonSerializer();
- // 設(shè)置value的序列化規(guī)則和 key的序列化規(guī)則
- template.setValueSerializer(jackson2JsonRedisSerializer);
- template.setKeySerializer(new StringRedisSerializer());
- template.afterPropertiesSet();
- return template;
- }
- /**
- * 處理redis序列化問(wèn)題
- * @return Jackson2JsonRedisSerializer
- */
- private Jackson2JsonRedisSerializer<Object> initJacksonSerializer() {
- Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
- ObjectMapper om = new ObjectMapper();
- om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
- //以下替代舊版本 om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
- om.activateDefaultTyping(om.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL);
- //bugFix Jackson2反序列化數(shù)據(jù)處理LocalDateTime類(lèi)型時(shí)出錯(cuò)
- om.disable(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS);
- // java8 時(shí)間支持
- om.registerModule(new JavaTimeModule());
- jackson2JsonRedisSerializer.setObjectMapper(om);
- return jackson2JsonRedisSerializer;
- }
- }
另一個(gè)是StringRedisTemplate,主要處理鍵值都是字符串的緩存,采用默認(rèn)就好。
6. 緩存?zhèn)€性化
使用Spring Cache做緩存的時(shí)候,有針對(duì)不同的key設(shè)置不同過(guò)期時(shí)間的場(chǎng)景。比如Jwt Token我想設(shè)置為一周過(guò)期,而驗(yàn)證碼我想設(shè)置為五分鐘過(guò)期。這個(gè)怎么實(shí)現(xiàn)呢?需要我們個(gè)性化配置RedisCacheManager。首先我通過(guò)枚舉來(lái)定義這些緩存及其TTL時(shí)間。例如:
- /**
- * 緩存定義枚舉
- *
- * @author felord.cn
- * @see cn.felord.kono.configuration.CacheConfiguration
- * @since 2020/8/17 21:40
- */
- public enum CacheEnum {
- /**
- * 用戶jwt token 緩存空間 ttl 7天
- */
- JWT_TOKEN_CACHE("usrTkn", 7 * 24 * 60 * 60),
- /**
- * 驗(yàn)證碼緩存 5分鐘ttl
- */
- SMS_CAPTCHA_CACHE("smsCode", 5 * 60);
- /**
- * 緩存名稱(chēng)
- */
- private final String cacheName;
- /**
- * 緩存過(guò)期秒數(shù)
- */
- private final int ttlSecond;
- CacheEnum(String cacheName, int ttlSecond) {
- this.cacheName = cacheName;
- this.ttlSecond = ttlSecond;
- }
- public String cacheName() {
- return this.cacheName;
- }
- public int ttlSecond() {
- return this.ttlSecond;
- }
- }
這樣就能很清楚地描述個(gè)性化的緩存了。
然后我們通過(guò)向Spring IoC分別注入RedisCacheConfiguration和RedisCacheManagerBuilderCustomizer來(lái)個(gè)性化配置,你可以留意CacheEnum是如何工作的。如果你有其它的個(gè)性化需要也可以對(duì)這兩個(gè)配置類(lèi)進(jìn)行定制化。
- import cn.felord.kono.enumeration.CacheEnum;
- import org.springframework.boot.autoconfigure.cache.CacheProperties;
- import org.springframework.boot.autoconfigure.cache.RedisCacheManagerBuilderCustomizer;
- import org.springframework.cache.annotation.EnableCaching;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.data.redis.cache.RedisCacheConfiguration;
- import org.springframework.data.redis.cache.RedisCacheManager;
- import org.springframework.data.redis.core.RedisTemplate;
- import org.springframework.data.redis.serializer.RedisSerializationContext;
- import java.time.Duration;
- import java.util.EnumSet;
- import java.util.stream.Collectors;
- /**
- * redis 緩存配置.
- *
- * @author felord.cn
- * @since 2020 /8/17 20:14
- */
- @EnableCaching
- @Configuration
- public class CacheConfiguration {
- /**
- * Redis cache configuration.
- *
- * @param redisTemplate the redis template
- * @return the redis cache configuration
- */
- @Bean
- public RedisCacheConfiguration redisCacheConfiguration(RedisTemplate<Object, Object> redisTemplate, CacheProperties cacheProperties) {
- // 參見(jiàn) spring.cache.redis
- CacheProperties.Redis redisProperties = cacheProperties.getRedis();
- RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
- // 緩存的序列化問(wèn)題
- .serializeValuesWith(RedisSerializationContext.SerializationPair
- .fromSerializer(redisTemplate.getValueSerializer()));
- if (redisProperties.getTimeToLive() != null) {
- // 全局 TTL 時(shí)間
- redisCacheConfiguration = redisCacheConfiguration.entryTtl(redisProperties.getTimeToLive());
- }
- if (redisProperties.getKeyPrefix() != null) {
- // key 前綴值
- redisCacheConfiguration = redisCacheConfiguration.prefixCacheNameWith(redisProperties.getKeyPrefix());
- }
- if (!redisProperties.isCacheNullValues()) {
- // 默認(rèn)緩存null值 可以防止緩存穿透
- redisCacheConfiguration = redisCacheConfiguration.disableCachingNullValues();
- }
- if (!redisProperties.isUseKeyPrefix()) {
- // 不使用key前綴
- redisCacheConfiguration = redisCacheConfiguration.disableKeyPrefix();
- }
- return redisCacheConfiguration;
- }
- /**
- * Redis cache manager 個(gè)性化配置緩存過(guò)期時(shí)間.
- * @see RedisCacheManager,CacheEnum
- * @return the redis cache manager builder customizer
- */
- @Bean
- public RedisCacheManagerBuilderCustomizer redisCacheManagerBuilderCustomizer(RedisCacheConfiguration redisCacheConfiguration) {
- return builder -> builder.cacheDefaults(redisCacheConfiguration)
- // 自定義的一些緩存配置初始化 主要是特定緩存及其ttl時(shí)間
- .withInitialCacheConfigurations(EnumSet.allOf(CacheEnum.class).stream()
- .collect(Collectors.toMap(CacheEnum::cacheName,
- cacheEnum -> redisCacheConfiguration.entryTtl(Duration.ofSeconds(cacheEnum.ttlSecond())))));
- }
- }
個(gè)性化的同時(shí)我們可以通過(guò)@EnableCaching開(kāi)啟Spring Cache緩存支持。關(guān)于Spring Cache的細(xì)節(jié)可以通過(guò)文章Spring Cache 詳解來(lái)了解。

驗(yàn)證Spring Cache Redis緩存?zhèn)€性化
請(qǐng)注意,只有通過(guò)Spring Cache操作緩存才會(huì)達(dá)到上圖的效果。命令行操作需要顯式的聲明指令。
7. 總結(jié)
最近事情比較多,所以難得抽出時(shí)間來(lái)搞一搞。如果你在實(shí)際開(kāi)發(fā)中遇到需要整合的功能也可以告訴我,同時(shí)如果你發(fā)現(xiàn)整合中的一些缺陷或者 Bug 請(qǐng)?zhí)峤? ISSUE。多多關(guān)注:碼農(nóng)小胖哥,跟我一起整合開(kāi)發(fā)腳手架。
本文轉(zhuǎn)載自微信公眾號(hào)「碼農(nóng)小胖哥」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系碼農(nóng)小胖哥公眾號(hào)。碼農(nóng)小胖哥