SpringBoot整合redis(redis支持单节点和集群)

目录

    • 一、简介
    • 二、Maven依赖
    • 三、RedisAutoConfiguration源码
    • 四、自定义RedisConfig配置(核心)
      • 4.1 配置源代码
      • 4.2 单节点配置
      • 4.2 集群节点配置
    • 五、实践
      • 5.1、测试源代码
      • 5.2、测试结果
      • 5.3、redis中Java 8值的序列化效果图
    • 结语

一、简介

  Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。通过这种方式,Spring Boot致力于在蓬勃发展的快速应用开发领域(rapid application development)成为领导者。

  Redis是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。

  今天我们用一个maven项目实战一个Spring BootRedis的详细整合与使用。

二、Maven依赖

  注意:本文中的com.fasterxml.jackson使用的依赖的版本号为2.9.10,不要使用到最新的版本2.12.4。Spring BootRedis的redis的版本最好使用最新版(当前最新为2.5.2),并且保持两者版本一致,避免不兼容的情况。


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0modelVersion>
    <parent>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-parentartifactId>
        
        <version>2.5.2version>
        <relativePath/>
    parent>

    <groupId>com.aliangroupId>
    <artifactId>redisartifactId>
    <version>0.0.1-SNAPSHOTversion>
    <name>redisname>
    <description>spring boot演示redisdescription>

    <properties>
        <project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>
        <project.package.directory>targetproject.package.directory>
        <java.version>1.8java.version>
        
        <jackson.version>2.9.10jackson.version>
        
        <fastjson.version>1.2.68fastjson.version>
    properties>


    <dependencies>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starterartifactId>
            <version>${parent.version}version>
        dependency>

        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-data-redisartifactId>
            <version>${parent.version}version>
        dependency>

        
        <dependency>
            <groupId>com.fasterxml.jackson.coregroupId>
            <artifactId>jackson-databindartifactId>
            <version>${jackson.version}version>
        dependency>

        
        <dependency>
            <groupId>com.fasterxml.jackson.datatypegroupId>
            <artifactId>jackson-datatype-jsr310artifactId>
            <version>${jackson.version}version>
        dependency>

        
        <dependency>
            <groupId>com.alibabagroupId>
            <artifactId>fastjsonartifactId>
            <version>${fastjson.version}version>
        dependency>

    dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-maven-pluginartifactId>
            plugin>
        plugins>
    build>

project>

三、RedisAutoConfiguration源码

首先我们先看下RedisAutoConfiguration的源码,源码路径:spring-boot-autoconfigure-2.5.2.jar包中org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration

package org.springframework.boot.autoconfigure.data.redis;

import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;

@Configuration(
    proxyBeanMethods = false
)
@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<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
     
        RedisTemplate<Object, Object> template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
     
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }
}

  通过源码可以看出,SpringBoot自动帮我们在容器中生成了一个RedisTemplate和一个StringRedisTemplate。可是实际使用中会有几个问题:

  • RedisTemplate的泛型是,编码时候经常要做类型转换
  • RedisTemplate没设置key及value的序列化方式,容易导致存储乱码,或者解析异常,查看也不友好

  我们实际中更常用到的是形式的RedisTemplate,当看到这个@ConditionalOnMissingBean注解后,我们就可以实现自己的RedisTemplate了。@ConditionalOnMissingBean 是修饰Bean的一个注解,仅当 BeanFactory 中不包含指定的 bean class 和/或 bean name 时条件匹配。
  我们顺便可以看看ConditionalOnMissingBean 源码

package org.springframework.boot.autoconfigure.condition;

import java.lang.annotation.Annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Conditional;

@Target({
     ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional({
     OnBeanCondition.class})
public @interface ConditionalOnMissingBean {
     
    Class<?>[] value() default {
     };

    String[] type() default {
     };

    Class<?>[] ignored() default {
     };

    String[] ignoredType() default {
     };

    Class<? extends Annotation>[] annotation() default {
     };

    String[] name() default {
     };

    SearchStrategy search() default SearchStrategy.ALL;

    Class<?>[] parameterizedContainer() default {
     };
}

  从ConditionalOnMissingBean 的源码以及上面RedisAutoConfiguration 的源码,我们知道如果Spring容器中有bean nameredisTemplate对象了,这个自动配置的RedisTemplate就不会实例化。因此我们可以直接自己写个配置类,配置RedisTemplate

四、自定义RedisConfig配置(核心)

废话不多说,直接上代码。这里再次提醒:com.fasterxml.jackson使用的依赖的版本号为2.9.10,具体可以看下我的maven依赖。

4.1 配置源代码

RedisConfig.java

package com.alian.redis.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
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.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;

@Configuration
public class RedisConfig {
     

    /**
     * redis配置
     *
     * @param redisConnectionFactory
     * @return
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
     
        // 实例化redisTemplate
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        //设置连接工厂
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        // key采用String的序列化
        redisTemplate.setKeySerializer(keySerializer());
        // value采用jackson序列化
        redisTemplate.setValueSerializer(valueSerializer());
        // Hash key采用String的序列化
        redisTemplate.setHashKeySerializer(keySerializer());
        // Hash value采用jackson序列化
        redisTemplate.setHashValueSerializer(valueSerializer());
        //执行函数,初始化RedisTemplate
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

    /**
     * key类型采用String序列化
     *
     * @return
     */
    private RedisSerializer<String> keySerializer() {
     
        return new StringRedisSerializer();
    }

    /**
     * value采用JSON序列化
     *
     * @return
     */
    private RedisSerializer<Object> valueSerializer() {
     
        //设置jackson序列化
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        //设置序列化对象
        jackson2JsonRedisSerializer.setObjectMapper(getMapper());
        return jackson2JsonRedisSerializer;
    }


    /**
     * 使用com.fasterxml.jackson.databind.ObjectMapper
     * 对数据进行处理包括java8里的时间
     *
     * @return
     */
    private ObjectMapper getMapper() {
     
        ObjectMapper mapper = new ObjectMapper();
        //设置可见性
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        //默认键入对象
        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        //设置Java 8 时间序列化
        JavaTimeModule timeModule = new JavaTimeModule();
        timeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        timeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
        timeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
        timeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        timeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
        timeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
        //禁用把时间转为时间戳
        mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
        mapper.registerModule(timeModule);
        return mapper;
    }
}

4.2 单节点配置

如果redis是单节点,application.properties 配置如下:(为了演示方便,我这里Redis数据库索引使用的是1)

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

4.2 集群节点配置

如果是redis集群,application.properties 配置如下:(集群节点至少需要3组主从)

#集群地址配置,值为 host:port,用逗号分隔
spring.redis.cluster.nodes=192.168.0.111:6379,192.168.0.113:6379,192.168.0.101:6379,192.168.0.102:6379,192.168.0.103:6379,192.168.0.114:6379,192.168.0.104:6379

五、实践

整合完毕后,不管是单节点的redis,还是redis集群,都可以进行如下的测试:

5.1、测试源代码

RedisService .java

package com.alian.redis.service;

import com.alibaba.fastjson.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;

@Service
public class RedisService {
     

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * PostConstruct在构造函数之后,init()方法之前执行。
     */
    @PostConstruct
    public void redisTestWithPostConstruct() {
     
        System.out.println("--------------------redis中String(字符串)测试开始--------------------");
        stringRedis();
        System.out.println("--------------------redis中String(字符串)测试结束--------------------");
        System.out.println();
        System.out.println("--------------------redis中list(列表)测试开始--------------------");
        listRedis();
        System.out.println("--------------------redis中list(列表)测试List结束--------------------");
        System.out.println();
        System.out.println("--------------------redis中Hash(散列)开始--------------------");
        hashRedis();
        System.out.println("--------------------redis中Hash(散列)结束--------------------");
    }


    private void stringRedis() {
     
        // 定义一个json对象
        JSONObject json = new JSONObject();
        json.put("title", "SpringBoot整合redis(单节点)");
        json.put("author", "Alian");
        json.put("webUrl", "https://blog.csdn.net/Alian_1223");
        json.put("publishTime", LocalDateTime.now());
        //定义也给缓存的key
        String cacheKey = "com.alian.csdn.redis.string";
        // 缓存json字符串,同时设置过期时间为5分钟
        redisTemplate.opsForValue().set(cacheKey, json.toJSONString(), 40, TimeUnit.SECONDS);
        //从缓存中获取到的缓存的值
        String jsonValue = (String) redisTemplate.opsForValue().get(cacheKey);
        System.out.println("[String(字符串)],从缓存中获取到的缓存的值:" + jsonValue);
    }

    private void listRedis() {
     
        //定义一个List对象
        List<Object> list = new ArrayList<>();
        list.add("apple");
        list.add("orange");
        list.add("pear");
        //加入一个java8时间,检测是否序列化
        list.add(LocalDateTime.now());
        //定义也给缓存的key
        String cacheKey = "com.alian.csdn.redis.list";
        // 缓存list,同时设置过期时间为30秒(列表的左侧放入)
        redisTemplate.opsForList().leftPushAll(cacheKey, list);
        redisTemplate.expire(cacheKey, 3, TimeUnit.MINUTES);
        //从缓存中获取到的缓存的值
        List<Object> value = redisTemplate.opsForList().range(cacheKey, 0, -1);
        System.out.println("[list(列表)],从缓存中获取到的缓存的值:" + value);
    }

    private void hashRedis() {
     
        //定义一个map对象
        Map<String, Object> map = new HashMap<>();
        map.put("name", "梁南生");
        map.put("sex", "男");
        map.put("salary", "120000");
        map.put("birth", LocalDate.of(1995, 8, 12));
        //定义也给缓存的key
        String cacheKey = "com.alian.csdn.redis.hash";
        // 缓存map,同时设置过期时间为2小时
        redisTemplate.opsForHash().putAll(cacheKey, map);
        redisTemplate.expire(cacheKey, 2, TimeUnit.HOURS);
        //从缓存中获取姓名和生日
        List<Object> list = redisTemplate.opsForHash().multiGet(cacheKey, Arrays.asList("name", "birth"));
        System.out.println("[Hash(散列)],从缓存中获取到的姓名和生日为:" + list);
    }
}

  这里我教大家一个新的方法进行简单的单元测试,那就是注解@PostConstruct,这个注解不是Spring的注解,而是java原生的javax.annotation.PostConstruct,使用上需要注意的如下:

  • 非静态方法才能使用
  • 被注解的方法不能有参数
  • 被注解的方法不能有返回值
  • 被注解的方法不能抛出异常
  • 被注解的方法只执行一次

缺点是服务启动时间稍慢一点,启动时加载的顺序
Constructor(构造方法) -> @Autowired(依赖注入) -> @PostConstruct(注释的方法)

5.2、测试结果

运行结果:

--------------------redis中String(字符串)测试开始--------------------
[String(字符串)],从缓存中获取到的缓存的值:{"publishTime":"2021-07-14T17:11:47.275","author":"Alian","webUrl":"https://blog.csdn.net/Alian_1223","title":"SpringBoot整合redis(单节点)"}
--------------------redis中String(字符串)测试结束--------------------

--------------------redis中list(列表)测试开始--------------------
[list(列表)],从缓存中获取到的缓存的值:[2021-07-14 17:11:47, pear, orange, apple]
--------------------redis中list(列表)测试List结束--------------------

--------------------redis中Hash(散列)开始--------------------
[Hash(散列)],从缓存中获取到的姓名和生日为:[梁南生, 1995-08-12]
--------------------redis中Hash(散列)结束--------------------

5.3、redis中Java 8值的序列化效果图

最后我们看下Java8的时间是否已经按照我们的格式(yyyy-MM-dd HH:mm:ss)序列化好,从客户端查看如下图所示:
SpringBoot整合redis(redis支持单节点和集群)_第1张图片
关于Java8时间的序列化需要注意的是,序列化对象的值类型是Java8时间类型,而不是某个对象如json或字符串里包含一个java8时间类型的属性,至此,我们的redis里的key,value的序列化方式也实现了。

结语

  本文主要把Spring BootRedis进行详细整合,包括单节点redisredis集群,也对部分原理进行简单介绍,这个整合在实际开发中是比较实用和灵活的,比如灵活的切换单节点redis和redis集群,使用上并没有半点不同。

你可能感兴趣的:(Redis笔记,java,spring,boot,redis)