SpringBoot 下集成 Redis

作者: HikariCP | 来源:发表于2017-11-06 02:13 被阅读127次

    GitHub 地址

    访问GitHub下载最新源码:https://github.com/JYG0723/SpringBootAction/tree/master/chapter4


    Spring Boot 中除了对常用的关系型数据库提供了优秀的自动化支持之外,对于很多 NoSQL 数据库一样提供了自动化配置的支持,包括:Redis,MongoDB,,Elasticsearch,,Solr 和 Cassandra。


    Redis


    Pom

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

    Spring Boot提供的数据访问框架 Spring Data Redis 基于Jedis,源码:

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

    properties

    # REDIS (RedisProperties)
    # Redis数据库索引(默认为0)
    spring.redis.database=0
    # Redis服务器地址
    spring.redis.host=localhost
    # Redis服务器连接端口
    spring.redis.port=6379
    # Redis服务器连接密码(默认为空)
    spring.redis.password=
    # 连接池最大连接数(使用负值表示没有限制)
    spring.redis.pool.max-active=8
    # 连接池最大阻塞等待时间(使用负值表示没有限制)
    spring.redis.pool.max-wait=-1
    # 连接池中的最大空闲连接
    spring.redis.pool.max-idle=8
    # 连接池中的最小空闲连接
    spring.redis.pool.min-idle=0
    # 连接超时时间(毫秒)
    spring.redis.timeout=0
    

    其中spring.redis.database的配置通常使用0即可,Redis 在配置的时候可以设置数据库数量,默认为16,可以理解为数据库的 schema

    因为有好多人曾问我是如何看源码的,(其实我不怎么看,偶尔瞟两眼),然后这里我就简单说一下我是怎么过流程的。


    看源码:

    这里对着属性,CTRL + B

    到了这里,我们就进入了源码的世界。

    同样可以根据 CTRL + B 定位到我们的 Libraries 中类所在的位置。我经常定位到这里会看看引入的包的目录结构,或者随便点点看看该包下的其他相关类。

    再对着 RedisProperties 类 CTRL + B。我们可以看到很多处引用到该 RedisProperties 类的地方,其中有包引入import ···,还有专门为该类开启关于@ConfigurationProperties注解的支持,@EnableConfigurationProperties。还有实例的声明,还有作为构造参数的注入,还有静态变量的引用,还有配置文件对应关系的指定。等等,会有很多作用处被检索到。我们需要根据自己感兴趣的内容点进去一探究竟。这里很明显我们进入了 RedisAutoConfiguration(看名字都知道是你)。

    一进来就看到了很多的条件。众多的@ConditionalOnClass摆在面前。我们怎么样能知道这些被要求存在于 classpath 的类是否存在于 classpath 呢? 很简单,

    我们按住 CTRL 鼠标移到该类上方,就可以看到这一行详细信息,这一段信息向我们交代了它所被包含在 maven 的哪个包中。在什么包下,还顺带告诉了我们他继承了 AbstractRedisConnection 类。是不是很直观很清晰。下面我们还看到一个引用了的@ConditionalOnClass的配置 bean。我们还用刚才的小套路,发现咦不对,乍一看。如果我们对一些包依赖不清晰的话,以为自己没有在 maven 中引入这个依赖呢,虽然我们确实没有显示的依赖。但是我们不妨在右侧的 MavenProjects 栏下的树结构找一找看看是否真的没有引入该依赖呢。就这样,我们找到了它。原来 springboot 打包好的 starter-data-redis 下的 redis.clients:jedis 依赖已经为我们传递依赖引入了该 commons 依赖。

    到这里,我们就可以确认这个 springboot 为我们自动配置的配置类可以作用了。同时它下面的静态内部类 RedisConnectionConfiguration 也可以起作用了。是不是很有感觉呢 ~ 到这里我们先暂停存档,待我们进行到后面继续带大家探索。


    测试访问

    我们引入了各种依赖,也为其配置了属性,也走了一遍流程。我们觉得他应该是可以运作了。可是到底是可以了吗?我们也不确定,那就来写一个测试用例吧。

    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class Chapter4ApplicationTests {
    
        @Autowired
        private StringRedisTemplate stringRedisTemplate;
    
        @Test
        public void contextLoads() {
        }
    
        @Test
        public void test() throws Exception {
            stringRedisTemplate.opsForValue().set("aaa", "111");
    
            Assert.assertEquals("111", stringRedisTemplate.opsForValue().get("aaa"));
        }
    
    }
    

    通过上面这段极为简单的测试案例演示了如何通过自动配置的StringRedisTemplate对象进行 Redis 的读写操作,该对象从命名中就可注意到支持的是 String 类型 StringRedisTemplate extends RedisTemplate<String, String>。如果有使用过spring-data-redis的开发者一定熟悉RedisTemplate<K, V>接口,StringRedisTemplate就相当于RedisTemplate<String, String>的实现。

    除了 String 类型,实战中我们还经常会在 Redis 中存储对象,这时候我们就会想是否可以使用类似RedisTemplate<String, User>来初始化并进行操作。但是 Spring Boot 并不支持直接使用,需要我们自己实现RedisSerializer<T>接口来对传入对象进行序列化和反序列化,下面我们通过一个实例来完成对象的读写操作。


    Po

    public class User implements Serializable {
    
        // 手动声明较好
        private static final long serialVersionUID = -1L;
    
        private String username;
        private String password;
    
        public User(String username, String password) {
            this.username = username;
            this.password = password;
        }
    
        public String getUsername() {
            return username;
        }
    
        public void setUsername(String username) {
            this.username = username;
        }
    
        public String getPassword() {
            return password;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    }
    

    实现对象的序列化接口

    public class RedisObjectSerializer implements RedisSerializer<Object> {
    
        private Converter<Object, byte[]> serializer = new SerializingConverter();
        private Converter<byte[], Object> deserializer = new DeserializingConverter();
    
        static final byte[] EMPTY_ARRAY = new byte[0];
    
        @Override
        public Object deserialize(byte[] bytes) {
            if (isEmpty(bytes)) {
                return null;
            }
            try {
                return deserializer.convert(bytes);
            } catch (Exception ex) {
                throw new SerializationException("Cannot deserialize", ex);
            }
        }
    
        @Override
        public byte[] serialize(Object object) {
            if (object == null) {
                return EMPTY_ARRAY;
            }
            try {
                return serializer.convert(object);
            } catch (Exception ex) {
                return EMPTY_ARRAY;
            }
        }
    
        private boolean isEmpty(byte[] data) {
            return (data == null || data.length == 0);
        }
    }
    

    到这里你可能会问,为什么你要配置这个呢?这个不急,我们后面详谈。


    针对 User 的 RedisTemplate 实例

    @Configuration
    public class RedisConfig {
    
       @Bean
       JedisConnectionFactory jedisConnectionFactory() { // 方法名即对象名
           return new JedisConnectionFactory();
       }
    
       @Bean
       public RedisTemplate<String, User> redisTemplate(RedisConnectionFactory redisConnectionFactory {
           RedisTemplate<String, User> template = new RedisTemplate<String, User>();
           template.setConnectionFactory(jedisConnectionFactory());
           template.setKeySerializer(new StringRedisSerializer());
           template.setValueSerializer(new RedisObjectSerializer());
           return template;
       }
    }
    

    到了这里。你肯定会问我,为什么要这样配?这样配好杂乱,我怎么样就可以记住它呢?别忘了我们相当于配置了一个 RedisTemplate 类的子类哦。既然同时其子类,为什么我们不参考那个被自动配置的 StringRedisTemplate 类呢?

    所以我们继续我们的源码:

    是不是瞬间明白了我们前面为什么要创建 RedisObjectSerializer 类来实现对象的序列化接口。因为我们在实现 RedisTemplate 类的时候需要引用我们关于对象的序列化方式。既然要创建的序列化接口,就要去参谋一下别人的序列化接口是怎么实现的不是吗?所以来到 StringRedisSerizlizer 类。

    照猫画虎我们也来一遍自己的实现。就 ok 了

    StringRedisTemplate 类的第一个构造函数看完了。我们再来看一看第二个构造函数有什么要求。

    它要求我们配置一个 RedisConnectionFactory 接口式的连接工厂。这里我们不用 RedisConnectionFactory 默认的实现。既然我们是 Java 应用 Redis。那就用我们依赖中包含的传递依赖 jedis (即 Java 语言来操作 Redis) 中的 JedisConnectionFactory 类来创建该链接工厂。同时该 JedisConnectionFactory 类也是专门针对 Java 语言对该RedisConnectionFactory 接口的实现类。由于 SpringBoot 为我们自动配置 JedisConnectionFactory 的要求是 classpath 中不包含 RedisConnectionFactory 。这里我们只有显示声明。同时也可以在 pom 文件中将其排除出去。

    除此之外。原来在 StringRedisTemplate 中显示调用的 afterproperties() 方法这里由于我们实现了自己的实例化接口也不再显示调用。该方法由 RedisTemplate 进行了扩展,为我们键值以及 hashkey,hashvalue 判断如果为空则引用默认的实例化方案。由于首先调用了其父类的该方法,我们不妨进去看看。一目了然,利用断言确保我们配置了连接工厂而已。

    到这里我们的源码探索就结束了。是不是觉得看源码其实也没有那么难?


    测试

    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class UserSerializableTest {
    
        @Autowired
        private RedisTemplate<String, User> redisTemplate;
    
        @Test
        public void test() throws Exception {
    
            //保存对象
            User user = new User("ji", 20);
            redisTemplate.opsForValue().set(user.getUsername(), user);
    
            user = new User("yong", 30);
            redisTemplate.opsForValue().set(user.getUsername(), user);
    
            user = new User("guang", 40);
            redisTemplate.opsForValue().set(user.getUsername(), user);
    
            Assert.assertEquals(20, redisTemplate.opsForValue().get("ji").getAge().longValue());
            Assert.assertEquals(30, redisTemplate.opsForValue().get("yong").getAge().longValue());
            Assert.assertEquals(40, redisTemplate.opsForValue().get("guang").getAge().longValue());
        }
    }
    

    可以看到我们三个对象已经成功序列化并存储到 Redis 中啦。

    相关文章

      网友评论

        本文标题:SpringBoot 下集成 Redis

        本文链接:https://www.haomeiwen.com/subject/gvjkmxtx.html