SpringBoot 2.x 整合 redis 做缓存(RedisTemplate)


原文链接
参考博文1,点击这里实现
参考博文2,点击这里实现
参考博文3,大多数类的解释,点击这里
注解的使用,点击这里

SpringBoot 2.x 整合 redis 做缓存

SpringBoot 2.0在今年3月份正式发布,公司新起的项目中就使用了SpringBoot 2.0,相比1.0还是有比较多的改动。SpringBoot 自2.0起不再支持jdk1.8以下的版本,jdk1.8中引入的default关键字,使接口可以提供方法的默认实现,SpringBoot 2.0中大量使用了该特性,因此在刚从1.0转为2.0时,很多类会提示已过期(deprecated),就是因为在1.0中,SpringBoot使用一个抽象类来提供接口方法的默认实现,用户在使用时需要继承该抽象类,实现自己的功能;更新到2.0后,无需在继承抽象类,直接实现某个接口,重写自己需要的方法即可。


一、redis的相关配置

1、引入spring-redis的依赖
首先使用maven引入spring-redis相关的依赖,2.0中使用spring-boot-starter-data-redis代替了原来的spring-boot-starter-redis

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2、配置redis数据库

在SpringBoot的application.yml配置文件中配置redis数据库的相关信息,这里改动主要有两点,其一是时间相关的属性,如spring.redis.timeout,在1.0中,时间相关的配置参数类型为int,默认单位为毫秒,配置中只需指定具体的数字即可,而在2.0中,时间相关的配置的参数类型都改为了jdk1.8的Duration,因此在配置文件中配置redis的连接超时时间timeout时,需要加入时间单位,如60s;其二是,在2.0中配置redis的连接池信息时,不再使用spring.redis.pool的属性,而是直接使用redis的lettuce或jedis客户端来配置,具体配置信息如下:

spring:
  redis:
    host: 127.0.0.1
    port: 6379
    database: 0
    timeout: 60s  # 数据库连接超时时间,2.0 中该参数的类型为Duration,这里在配置的时候需要指明单位
    # 连接池配置,2.0中直接使用jedis或者lettuce配置连接池
    jedis:
      pool:
        # 最大空闲连接数
        max-idle: 500
        # 最小空闲连接数
        min-idle: 50
        # 等待可用连接的最大时间,负数为不限制
        max-wait:  -1s
        # 最大活跃连接数,负数为不限制
        max-active: -1

3、配置CacheManager

通过配置Spring的CacheManager为redis,即可指定使用redis做缓存,具体的配置方式跟1.0也有所不同,在1.0中使用RedisTemplate即可实例化一个RedisCacheManager:RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);,在2.0中删除了这个构造器,同时也不可以通过之前的setDefaultExpiration方法设置默认的缓存过期时间等,在新版本中可以通过以下的两种方式构造一个RedisCacheManager:

通过RedisCacheManager的静态方法create:

@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
    RedisCacheManager cacheManager = RedisCacheManager.create(factory);
    return cacheManager;
}

这样产生的cacheManager只是使用Spring提供的默认配置

通过Spring提供的RedisCacheConfiguration类,构造一个自己的redis配置类,从该配置类中可以设置一些初始化的缓存命名空间、及对应的默认过期时间等属性,再利用RedisCacheManager中的builder.build()的方式生成cacheManager:

@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
    RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();  // 生成一个默认配置,通过config对象即可对缓存进行自定义配置
    config = config.entryTtl(Duration.ofMinutes(1))     // 设置缓存的默认过期时间,也是使用Duration设置
            .disableCachingNullValues();     // 不缓存空值

    // 设置一个初始化的缓存空间set集合
    Set<String> cacheNames =  new HashSet<>();
    cacheNames.add("my-redis-cache1");
    cacheNames.add("my-redis-cache2");

    // 对每个缓存空间应用不同的配置
    Map<String, RedisCacheConfiguration> configMap = new HashMap<>();
    configMap.put("my-redis-cache1", config);
    configMap.put("my-redis-cache2", config.entryTtl(Duration.ofSeconds(120)));

    RedisCacheManager cacheManager = RedisCacheManager.builder(factory)     // 使用自定义的缓存配置初始化一个cacheManager
            .initialCacheNames(cacheNames)  // 注意这两句的调用顺序,一定要先调用该方法设置初始化的缓存名,再初始化相关的配置
            .withInitialCacheConfigurations(configMap)
            .build();
    return cacheManager;
}

第三种在直接在CacheManager中解决乱码的问题。
原文,点击这里

@Configuration
@EnableCaching
@Slf4j
public class RedisConfig extends CachingConfigurerSupport {

    @Value("${spring.application.name:unknown}")
    private String appName;
    @Value("${spring.redis.timeToLive:600s}")
    private Duration timeToLive;

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);

        //解决查询缓存转换异常的问题
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);

        // 配置序列化(解决乱码的问题)
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(timeToLive)
                .prefixKeysWith(appName + ":")
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
                // 是否允许控制存储
                .disableCachingNullValues();

        RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
                .cacheDefaults(config)
                .build();
        return cacheManager;
    }
    
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        template.setConnectionFactory(factory);
        //key序列化方式
        template.setKeySerializer(redisSerializer);
        //value序列化
        template.setValueSerializer(jackson2JsonRedisSerializer);
        //value hashmap序列化
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        return template;
    }
}

在自定义缓存配置的时候需要注意一点,RedisCacheConfiguration设置配置缓存的方法都是有返回值的,需要重新赋值给config对象,一开始没注意到这点时,发现怎么设置都不起作用。。。这里可以看一下源码,比如设置缓存时间的方法entryTtl:

/**
 *Set the ttl to apply for cache entries. Use {@link Duration#ZERO} to declare an eternal cache.
 *
 * @param ttl must not be {@literal null}.
 * @return new {@link RedisCacheConfiguration}.
 */
public RedisCacheConfiguration entryTtl(Duration ttl) {

    Assert.notNull(ttl, "TTL duration must not be null!");
    // 这里return了一个新的RedisCacheConfiguration对象,RedisCacheConfiguration的属性都是final修饰的
    return new RedisCacheConfiguration(ttl, cacheNullValues, usePrefix, keyPrefix, keySerializationPair,
            valueSerializationPair, conversionService);
}

再看一下默认的配置,包含了默认的缓存时间及序列化的相关配置:

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);
}

到这里从1.0更新到2.0时,redis缓存的相关配置就差不多了,其余的方法也不太多,大家有兴趣可以自己测试下哈。。。

补充:

  • RedisCache使用RedisCacheWriter接口,用于对redis的读写;
  • RedisCache可通过RedisCacheConfiguration配置常用选项;

    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {

        return RedisCacheManager.RedisCacheManagerBuilder.fromCacheWriter(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory))
                // 默认策略,未配置的 key 会使用这个
                .cacheDefaults(redisConfig(60))
                // 自定义 key 策略
                .withInitialCacheConfigurations(redisCacheConfigurationMap()).build();
    }
  • cacheDefaults方法用来指定默认配置,withInitialCacheConfigurations方法用来对各种缓存空间进行个性化配置。redisCacheConfigurationMap方法是一个以缓存名称为key,其对应的redis配置类为值得键值映射。这个需要在开发中自己进行配置。参考CacheNameEnum 。