SpringBoot 2.x 整合Redis

简介

在 SpringBoot 2.x 中,已经将地层的 Jedis 替换为了 Letteuce了。

  • jedis: 采用的直连,多个线程操作的话,是不安全的,如果想要避免不安全的,使用 jedis pool链接池,更像 BIO 模式。
  • lettuce:采用 netty,实例可以再多个线程中共享,不存在线程不安全的情况,可以减少线程数据,更像 NIO 模式。

看下底层依赖,我是使用的是 SpringBoot 2.1.9.RELEASE 这个版本,下面可以可以看到 lettuce 就是基于 netty 实现了。

整合 Redis

工程结构

第一步:创建一个 SpringBoot 工程

引入依赖包

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

<!-- 这里我用到了 lombok -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <scope>provided</scope>
</dependency>

 对应的配置文件 application.properties 内容

# 服务端口
server.port=8080

# redis 配置
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=

第二步:redisTemplate 说明

在  @Autowired  RedisTemplate 之前,我们先看下 Spring Boot 源码上面的 RedisTemplate 是怎么回事?我们自己打开对应的类文件,RedisAutoConfiguration.java

@Configuration
@ConditionalOnClass({RedisOperations.class})
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
    public RedisAutoConfiguration() {}

    @Bean
    @ConditionalOnMissingBean(    // 这里我们是可以自己写 redisTemplate 覆盖
        name = {"redisTemplate"}
    )
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        // 默认的 RedisTemplate 没有过多的设置,redis 对象都是需要序列化
        // 这里两个泛型都是 Object, 但是在开发实际中,我们一般使用的是 <String, Object>
        RedisTemplate<Object, Object> template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    @Bean
    @ConditionalOnMissingBean
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        //  由于 string 是 redis 中最常使用的类型,所以 SpringBoot 框架单独提了一个 bean 出来
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }
}

 这里我们仔细看下,这里使用了 @ConditionalOnMissingBean 注解,说明如果是程序中存在了对应的 redisTemplate Bean,就不会去加载(其实这个意思是说,我们可以自己去覆盖这个)。

下面会介绍下自己定义,新建一个 RedisConfig.class 类,

package com.wq.redis.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
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.StringRedisSerializer;

import java.util.Objects;


@Configuration
public class RedisConfig {

    /**
     * 自己定义一个 redisTemplate,并设置相关参数
     * @param redisConnectionFactory RedisConnectionFactory
     * @return RedisTemplate
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
        // 为了我们自己开发方便,一般直接用 <String, Object>
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);

        // Json 序列化配置
        // 使用 Jackson2JsonRedisSerializer 替换默认的 JdkSerializationRedisSerializer 来序列化和反序列化 redis的value值
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Objects.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);

        jackson2JsonRedisSerializer.setObjectMapper(om);

        // String 的序列化
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        template.setKeySerializer(stringRedisSerializer);
        template.setHashKeySerializer(stringRedisSerializer);       // hash的key也采用String的序列化方式

        // template.setValueSerializer(jackson2JsonRedisSerializer);   // value序列化方式采用jackson
        template.setValueSerializer(stringRedisSerializer);         // value 采用 String 的方式
        template.setHashValueSerializer(jackson2JsonRedisSerializer);    // hash的value序列化方式采用jackson
        template.afterPropertiesSet();

        return template;
    }
}

第三步:创建一个服务类,用于操作 redis

package com.wq.redis.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

@Service
public class RedisOperator {

    @Autowired
    private RedisTemplate redisTemplate;

    public String getKey(String key){
        // 操作 redis 的时候就感觉这里比较奇怪,看下面的截图
        return (String) redisTemplate.opsForValue().get(key);
    }

    public void setKey(String key, String value){
        redisTemplate.opsForValue().set(key, value);
    }

}

对于redis 的操作,这里其实是封装的一层,需要知道自己操作的是哪种类型,然后使用对应的 API 去操作。

第四步:最后增加一个 Controller,用于 web 访问

增加一个 封装返回值的类,就在这里使用了 lombok 插件

package com.wq.redis.controller;

import lombok.Data;

@Data
public class ResultModel<T> {

    private boolean success = false;    // 是否调用成功
    private String message = "";
    private T body ; // 返回值

    public ResultModel(){}

    public ResultModel(boolean success, String message){
        this.success = success;
        this.message = message;
    }

    public ResultModel(boolean success, String message, T object){
        this(success, message);
        this.body = object;
    }
}

外部访问的入口

package com.wq.redis.controller;

import com.wq.redis.service.RedisOperator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;

@RestController
@CrossOrigin(origins = "*")
public class Router {

    @Autowired
    private RedisOperator redisOperator;

    @GetMapping(value = "/getKey")
    public ResultModel getKey(String body) {
        ResultModel resultModel = new ResultModel();
        System.out.println(body);
        try {
            String result = redisOperator.getKey(body);
            resultModel.setBody(result);
            resultModel.setSuccess(true);
            resultModel.setMessage("请求成功!");
        } catch (Exception e) {
            resultModel.setSuccess(false);
            resultModel.setMessage(e.getMessage());
            resultModel.setBody(new HashMap<>());
        }
        return resultModel;
    }

    @RequestMapping("setKey")
    public ResultModel setKey(String key, String value){
        ResultModel resultModel = new ResultModel();
        System.out.println("key:" +key+", value :" + value);
        try {
            redisOperator.setKey(key, value);
            resultModel.setBody("");
            resultModel.setSuccess(true);
            resultModel.setMessage("请求成功!");
        } catch (Exception e) {
            resultModel.setSuccess(false);
            resultModel.setMessage(e.getMessage());
            resultModel.setBody(new HashMap<>());
        }
        return resultModel;
    }

}

第五步:浏览器测试

先设置一个值 

获取刚刚设置的值

通过源码看下 redisTemplate 执行 set 操作

我们分析下框架底层是怎么做 set 方法的

我们通过 idea 工具点击  RedisOperator 中的 set 方法,

redisTemplate.opsForValue().set(key, value)

 进去会发现,set 方法是在  ValueOperations<K, V> 接口里面

然后继续看实现类,会发现就只有一个实现类 DefaultValueOperations.java ,这里会有个 execute 方法,我们继续进去看

这里我们先看下 ValueDeserializingRedisCallback 定义了什么东西

 上面有一个 doInRedis 方法,这个就是下面 execute 方法回调的位置,然后这里的 inRedis,就是 DefaultValueOperations 中 set 方法声明的那个匿名内部类中定义的 inRedis 方法

接着我们一路点击 execute 进来,可以看到下面的这个 execute 方法(这个方法是很多redis 操作最后执行调用的方法,是一个核心的方法),我们刚刚传入对象在这里其实是一个回调方法

 最后总结下

通过 RedisTemplate 里面的 execute 方法,调用传入的 RedisCallback的 doInRedis方法完成 对RedisCallback回调,在 ValueDeserializingRedisCallback 类中去回调最终的方法。

猜你喜欢

转载自blog.csdn.net/qq_18948359/article/details/119780556