简介
在 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 类中去回调最终的方法。