• SpringBoot整合Redis(自动配置分析、切换Jedis、自定义序列化器)


    1. Redis介绍

    Redis是一个开源(BSD许可)的、内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如字符串(strings)、散列(hashes)、列表(lists)、集合(sets)、有序集合(sorted sets)与范围查询、bitmaps、hyperloglogs和地理空间(geospatial)索引半径查询。 Redis内置了复制(replication)、LUA脚本(Lua scripting)、LRU驱动事件(LRU eviction)、事务(transactions)和不同级别的磁盘持久化(persistence), 并通过Redis哨兵(Sentinel)和自动分区(Cluster)提供高可用性(high availability)

    2. 添加pom.xml依赖

            
                org.springframework.boot
                spring-boot-starter-data-redis
            
    
    • 1
    • 2
    • 3
    • 4

    可以看到,会自动添加spring-data-redis和lettuce(netty实现)的redis客户端
    spring-boot-starter-data-redis

    3. 自动配置分析

    查看spring-boot-autoconfigure-2.7.5.jar,,可以看到:

    • 绑定了RedisProperties配置类
    • 向IOC容器添加了LettuceConnectionConfiguration和JedisConnectionConfiguration这两个redis客户端的连接配置组件
    • 向IOC容器添加了RedisTemplate(key和value都是Object)和StringRedisTemplate(key和value都是String),用来操作redis
    package org.springframework.boot.autoconfigure.data.redis;
    .....省略部分......
    @AutoConfiguration
    @ConditionalOnClass({RedisOperations.class})
    @EnableConfigurationProperties({RedisProperties.class})
    @Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
    public class RedisAutoConfiguration {
    
        public RedisAutoConfiguration() {
        }
    
        @Bean
        @ConditionalOnMissingBean(
            name = {"redisTemplate"}
        )
        @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
        public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
            RedisTemplate template = new RedisTemplate();
            template.setConnectionFactory(redisConnectionFactory);
            return template;
        }
    
        @Bean
        @ConditionalOnMissingBean
        @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
        public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
            return new StringRedisTemplate(redisConnectionFactory);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    查看RedisProperties配置类,redis的配置由spring.redis开头的配置进行配置的

    .....省略部分......
    @ConfigurationProperties(
        prefix = "spring.redis"
    )
    public class RedisProperties {
    .....省略部分......
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    我们再来看LettuceConnectionConfiguration这个配置类,可以看到

    • LettuceConnectionConfiguration类添加到IOC容器的条件是:我们的的配置参数没有指定spring.redis.client-type,或配置参数spring.redis.client-type=lettuce
    • 会向IOC容器添加LettuceConnectionFactory组件,其继承了RedisConnectionFactory,可以从该Factory获取到redis客户端的连接
    @Configuration(
        proxyBeanMethods = false
    )
    @ConditionalOnClass({RedisClient.class})
    @ConditionalOnProperty(
        name = {"spring.redis.client-type"},
        havingValue = "lettuce",
        matchIfMissing = true
    )
    class LettuceConnectionConfiguration extends RedisConnectionConfiguration {
    .....省略部分......
        @Bean
        @ConditionalOnMissingBean({RedisConnectionFactory.class})
        LettuceConnectionFactory redisConnectionFactory(ObjectProvider builderCustomizers, ClientResources clientResources) {
            LettuceClientConfiguration clientConfig = this.getLettuceClientConfiguration(builderCustomizers, clientResources, this.getProperties().getLettuce().getPool());
            return this.createLettuceConnectionFactory(clientConfig);
        }
    .....省略部分......
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    我们再来看JedisConnectionConfiguration这个配置类,可以看到

    • JedisConnectionConfiguration类添加到IOC容器的条件是:
      1. pom.xml中添加了Jedis相关的依赖
      2. 我们的的配置参数指定了spring.redis.client-type=jedis
    • 会向IOC容器添加JedisConnectionFactory组件,其继承了RedisConnectionFactory,可以从该Factory获取到redis客户端的连接
    @Configuration(
        proxyBeanMethods = false
    )
    @ConditionalOnClass({GenericObjectPool.class, JedisConnection.class, Jedis.class})
    @ConditionalOnMissingBean({RedisConnectionFactory.class})
    @ConditionalOnProperty(
        name = {"spring.redis.client-type"},
        havingValue = "jedis",
        matchIfMissing = true
    )
    class JedisConnectionConfiguration extends RedisConnectionConfiguration {
    ......省略部分......
        @Bean
        JedisConnectionFactory redisConnectionFactory(ObjectProvider builderCustomizers) {
            return this.createJedisConnectionFactory(builderCustomizers);
        }
    ......省略部分......
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    4. application.properties配置

    说明:

    • 可以通过url指定连接信息,也可以单独指定各个连接信息
    • 明确使用lettuce连接池
    • 指定了连接池同时活跃的最大连接数,和最小空闲连接数
    # 格式:redis://username:password@IP:Port
    spring.redis.url=redis://default:redis123@192.168.28.12:6379
    
    # 单独指定redis各配置参数
    # spring.redis.username=default
    # spring.redis.password=redis123
    # spring.redis.host=192.168.28.12
    # spring.redis.port=6379
    
    spring.redis.client-type=lettuce
    
    # 连接池同时活跃的最大连接数
    spring.redis.lettuce.pool.max-active=10
    # 连接池最小空闲连接数
    spring.redis.lettuce.pool.min-idle=5
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    5. 连接Redis测试

    下面做了如下操作:

    • 先对一个key设置了一个值,再进行值的获取
    • 对一个key进行increment增量加1
    • 查看当前的redis连接池Class
    package com.hh.springboottest;
    
    import lombok.extern.slf4j.Slf4j;
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.data.redis.connection.RedisConnectionFactory;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.core.ValueOperations;
    
    @Slf4j
    @SpringBootTest
    public class MyApplicationTest {
    
        @Autowired
        RedisTemplate redisTemplate;
    
        @Autowired
        RedisConnectionFactory redisConnectionFactory;
    
        @Test
        public void queryDataTest() {
    	   
            ValueOperations operations = redisTemplate.opsForValue();
            operations.set("redis-key", "hello world");
            String redisKey = operations.get("redis-key");
            log.info("获取到的redis-key: {}", redisKey);
    
            // 对redis的key的value进行加1
            redisTemplate.opsForValue().increment("increment-key");
    
            log.info("当前使用的redis客户端是: {}", redisConnectionFactory.getConnection());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    运行程序,结果如下:

    2022-11-20 07:32:05.096  INFO 12476 --- [           main] com.hh.springboottest.MyApplicationTest  : 获取到的redis-key: hello world
    2022-11-20 07:32:05.101  INFO 12476 --- [           main] com.hh.springboottest.MyApplicationTest  : 当前使用的redis客户端是: org.springframework.data.redis.connection.lettuce.LettuceConnection@57466fb7
    2022-11-20 07:32:05.288  INFO 12476 --- [ionShutdownHook] com.alibaba.druid.pool.DruidDataSource   : {dataSource-1} closing ...
    2022-11-20 07:32:05.296  INFO 12476 --- [ionShutdownHook] com.alibaba.druid.pool.DruidDataSource   : {dataSource-1} closed
    
    • 1
    • 2
    • 3
    • 4

    6. 使用Jedis客户端

    在pom.xml添加依赖

            
            
                redis.clients
                jedis
            
    
    • 1
    • 2
    • 3
    • 4
    • 5

    然后修改application.properties参数文件。指定spring.redis.client-type=jedis。如下所示:

    spring.redis.client-type=jedis
    spring.redis.jedis.pool.max-active=10
    
    • 1
    • 2

    7. 自定义默认的序列化器

    默认使用JdkSerializationRedisSerializer序列化器,使用RedisTemplate保存User对象,在Redis中看到的是乱码,而且其它类型的框架也不能正确读取这个User对象。所以可以将User对象以json的方式进行保存

    如果没有RedisTemplate,SpringBoot会自动添加一个默认的RedisTemplate。所以我们可以自己给IOC容器添加我们自定义的RedisTemplate,使用GenericJackson2JsonRedisSerializer序列化器,允许Object类型的key和value,都可以被转为json进行存储。具体实现如下:

    package com.hh.springboottest.config;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.redis.connection.RedisConnectionFactory;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
    
    
    @Configuration
    public class RedisConfiguration {
    
        @Bean
        public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory /*会自动注入进来*/) {
            RedisTemplate template = new RedisTemplate<>();
            template.setConnectionFactory(redisConnectionFactory);
            template.setDefaultSerializer(new GenericJackson2JsonRedisSerializer());
            return template;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    8. 序列化问题

    1. LocalDateTime序列化问题
      对于java.time.LocalDateTime,保存到redis的是时间戳,从redis读取出来的也是时间戳。需要对保存的字段进行格式化。如下所示。这样保存和读取的就是指定的格式
      // import com.fasterxml.jackson.annotation.JsonFormat;
      @JsonFormat( pattern="yyyy-MM-dd HH:mm:ss")
      private LocalDateTime updateTime;
    
    • 1
    • 2
    • 3
    1. Map序列化不包含null值问题
      对于实体类的字段类型为java.util.Map,如果map中的value为null,则该key和value不会写入到redis,可以在属性上添加fastjson2的注解进行解决,如下所示
      // import com.alibaba.fastjson2.JSONWriter;
      // import com.alibaba.fastjson2.annotation.JSONField;
      @JSONField(serializeFeatures = JSONWriter.Feature.WriteMapNullValue)
      private Map idNameMap;
    
    • 1
    • 2
    • 3
    • 4
  • 相关阅读:
    第四章:JVM运行时参数
    音视频转换器 Permute 3 for mac中文
    Delphi终极版速扩展VCL应用程序
    项目一:使用 Spring + SpringMVC + Mybatis + lombok 实现网络五子棋
    FPGA运算
    c++知识点之 --输入输出
    动态规划基础入门【1】
    Docker的数据管理和端口映射实现容器访问
    C语言面试题30至39题
    咬文嚼图式的介绍二叉树、B树/B-树
  • 原文地址:https://blog.csdn.net/yy8623977/article/details/127942698