1.引子
今天做java开发的小伙伴们都很幸福,为什么这么说呢?因为springboot框架的存在,自从有了springboot框架以后
- 自动装配能力:除了必要的一些配置,大多数配置都不用再写了,告别了一堆堆的配置文件
- 自动管理依赖:只需要引入spring-boot-starter-xxx,需要的jar包以及jar包的版本,就都有了,不再需要到处去找jar包,不再有版本冲突之类的烦恼
- actuator:还能提供健康检查、应用监控的能力,真是幸福,没有比这个更幸福的了
于是,一个@SpringBootApplication注解,一个SpringApplication.run(xxx.class, args)方法,即锁定了应用开发的完美解决方案!是这样的吧
在日常开发中,使用框架给我们带来的最大便利,便是提升了开发效率,这很重要!
不过,我还是建议,我们做开发的小伙伴们,有必要翻一翻框架的源码,看一看底层的实现逻辑,甚至很多时候,结合业务需要,尝试去做一些重构,封装一些好用的工具,这对提升我们的技术能力,很重要!
我们知道在无论大小项目中,redis都充当了很多重要角色(人狠话不多),比如说
- 实现缓存解决方案
- 实现分布式解决方案,比如分布式锁
- 实现计数器解决方案
因此,今天我们干脆来分享如何优雅的封装一套redis客户端工具。即便我们知道在springboot项目中,已经有了现成的工具
- 引入spring-boot-starter-data-redis依赖
- 配置spring.redis.xxx必要的一些信息,比如主机、端口等
- 注入一个RedisTemplate实例
我们就可以在应用中方便的操作redis了。但是有时候,我们需要一些定制化,比如说
- get、set操作中,自动完成bean对象,与json字符串的序列化反序列化
- 统一维护管理key前缀
那就让我们开始吧!
2.案例
2.1.设计思考
假设在我们项目中,需要操作应用到redis,并且提出如下需求
- 将数据存储到redis,且支持任意bean类型
- 从redis获取数据,且支持任意bean类型
- 自增、自减操作
- 从redis删除数据操作
- 业务使用redis中,key必须统一维护管理,不能随意定义key,防止不同的业务key混乱、产生数据被覆盖的风险
需求提出来,我们稍加理解,主要需要做两件事情
- 提供一个统一的redis客户端工具类
- 提供一套统一的维护key的机制
于是我们开始设计,需要如下一些类
RedisConfig: 配置类,实现redis配置信息封装
RedisPoolFactory: RedisPool工厂,实现JedisPool实例的创建
RedisCommonService: 客户端服务工具类,提供操作redis的api
RedisKeyPrefix: key前缀接口,维护业务key的统一抽象(过期时间、与key前缀)
BaseKeyPrefix: key前缀的抽象实现
UserRedisKey: key前缀应用案例举例
因为比较简单,我直接列举了需要的相关类,下一小节我会附上代码,并结合相关的注解,相信你一看到就能明白。当然思路最重要,我更期望给小伙伴们在实际项目中带来一些思考!
2.2.设计实现
2.2.1.导入依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.38</version>
</dependency>
2.2.2.配置文件
spring:
redis:
host: 127.0.0.1
port: 6379
timeout2: 3
poolMaxTotal: 10
poolMaxIdle: 10
poolMaxWait: 3
2.2.3.配置类RedisConfig
/**
* redis配置类
*
* @author ThinkPad
* @version 1.0
* @date 2021/7/10 21:26
*/
@Component
@ConfigurationProperties(prefix = "spring.redis")
@Data
public class RedisConfig {
/**
*主机
*/
private String host;
/**
*端口
*/
private Integer port;
/**
* 连接超时,单位秒
*/
private Integer timeout2;
/**
*连接池最大连接数
*/
private Integer poolMaxTotal;
/**
*连接池最大空闲连接数
*/
private Integer poolMaxIdle;
/**
* 等待超时,单位秒
*/
private Integer poolMaxWait;
}
2.2.4.工厂类RedisPoolFactory
/**
* redis连接池工厂
*
* @author ThinkPad
* @version 1.0
* @date 2021/7/10 21:30
*/
@Component
public class RedisPoolFactory {
@Autowired
private RedisConfig redisConfig;
/**
* 创建RedisPool
* @return
*/
@Bean
public JedisPool createRedisPool(){
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxIdle(redisConfig.getPoolMaxIdle());
poolConfig.setMaxTotal(redisConfig.getPoolMaxTotal());
poolConfig.setMaxWaitMillis(redisConfig.getPoolMaxWait() * 1000);
JedisPool pool = new JedisPool(poolConfig, redisConfig.getHost(),
redisConfig.getPort(),
redisConfig.getTimeout2() * 1000);
return pool;
}
}
2.2.5.客户端工具类RedisCommonService
/**
* 通用redis工具类
*
* @author ThinkPad
* @version 1.0
* @date 2021/7/10 21:34
*/
@Component
public class RedisCommonService {
@Autowired
private JedisPool jedisPool;
/**
* ===================外部使用方法============================
*/
/**
* 设置key - value
* @param keyPrefix key前缀
* @param key key
* @param value 值
* @param <T>
* @return
*/
public <T> boolean set(RedisKeyPrefix keyPrefix, String key, T value){
// 将T对象,转换成字符串
String beanToString = beanToString(value);
if(beanToString == null){ return false; }
// redis操作
Jedis jedis = null;
try{
jedis = jedisPool.getResource();
int seconds = keyPrefix.expireSeconds();
if(seconds <= 0){
jedis.set(keyPrefix.keyPrefix() + key, beanToString);
}else{
jedis.setex(keyPrefix.keyPrefix() + key, seconds, beanToString);
}
return true;
}finally {
close(jedis);
}
}
/**
* 获取 key - value
* @param keyPrefix key前缀
* @param key key
* @param clazz 值类型
* @param <T>
* @return
*/
public <T> T get(RedisKeyPrefix keyPrefix, String key, Class<T> clazz){
Jedis jedis = null;
try{
jedis = jedisPool.getResource();
String realKey = keyPrefix.keyPrefix() + key;
String value = jedis.get(realKey);
return stringToBean(value, clazz);
}finally {
close(jedis);
}
}
/**
* 自增
* @param keyPrefix key前缀
* @param key key
* @return
*/
public Long incr(RedisKeyPrefix keyPrefix, String key){
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
String realKey = keyPrefix.keyPrefix() + key;
return jedis.incr(realKey);
}finally {
close(jedis);
}
}
/**
* 自减
* @param keyPrefix key前缀
* @param key key
* @return
*/
public Long decr(RedisKeyPrefix keyPrefix, String key){
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
String realKey = keyPrefix.keyPrefix() + key;
return jedis.decr(realKey);
}finally {
close(jedis);
}
}
/**
* 删除key
* @param keyPrefix
* @param key
* @return
*/
public boolean del(RedisKeyPrefix keyPrefix, String key){
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
String realKey = keyPrefix.keyPrefix() + key;
return jedis.del(realKey) > 0;
}finally {
close(jedis);
}
}
/**
* 检查key是否存在
* @param keyPrefix
* @param key
* @return
*/
public boolean exists(RedisKeyPrefix keyPrefix, String key){
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
String realKey = keyPrefix.keyPrefix() + key;
return jedis.exists(realKey);
}finally {
close(jedis);
}
}
/**
* ===================私有方法============================
*/
/**
* 将redis客户端,换回连接池
* @param jedis
*/
private void close(Jedis jedis){
if(jedis != null){
jedis.close();
}
}
/**
* 将bean对象,转换为json字符串
* @param value
* @param <T>
* @return
*/
private static <T> String beanToString(T value){
if(value == null){ return null; }
Class<?> clazz = value.getClass();
if(clazz == int.class
|| clazz == long.class
|| clazz == float.class
|| clazz == double.class
|| clazz == Number.class) {
return String.valueOf(value);
}else {
return JSON.toJSONString(value);
}
}
/**
* 将json字符串,转换成bean对象
* @param value
* @param clazz
* @param <T>
* @return
*/
private static <T> T stringToBean(String value, Class<T> clazz){
if(value == null || value.length() <= 0 || clazz == null) {
return null;
}
if(clazz == int.class || clazz == Integer.class) {
return (T)Integer.valueOf(value);
}else if(clazz == long.class || clazz == Long.class) {
return (T)Long.valueOf(value);
}else if(clazz == float.class || clazz == Float.class) {
return (T)Float.valueOf(value);
}else if(clazz == double.class || clazz == Double.class) {
return (T)Double.valueOf(value);
}else if(clazz == String.class) {
return (T)value;
}else {
return JSON.toJavaObject(JSON.parseObject(value), clazz);
}
}
}
2.2.6.统一抽象key前缀
接口RedisKeyPrefix
/**
* redis通用key
*
* @author ThinkPad
* @version 1.0
* @date 2021/7/10 21:36
*/
public interface RedisKeyPrefix {
/**
* 过期时间
* @return
*/
int expireSeconds();
/**
* key 前缀
* @return
*/
String keyPrefix();
}
抽象类BaseKeyPrefix
/**
* 抽象key实现
*
* @author ThinkPad
* @version 1.0
* @date 2021/7/10 21:39
*/
public abstract class BaseKeyPrefix implements RedisKeyPrefix {
private int second;
private String prefix;
/**
* 默认0表示永不过期
* @param prefix
*/
public BaseKeyPrefix(String prefix){
this(0, prefix);
}
/**
* 明确过期时间,与前缀
* @param second
* @param prefix
*/
public BaseKeyPrefix(int second, String prefix){
this.second = second;
this.prefix = prefix;
}
/**
* 过期时间
*
* @return
*/
@Override
public int expireSeconds() {
return second;
}
/**
* key 前缀
*
* @return
*/
@Override
public String keyPrefix() {
return getClass().getSimpleName() + ":" +prefix + ":";
}
}
应用案例UserRedisKey
/**
* 用户redis key
*
* @author ThinkPad
* @version 1.0
* @date 2021/7/10 21:45
*/
public class UserRedisKey extends BaseKeyPrefix {
private UserRedisKey(int second, String prefix){
super(second, prefix);
}
/**
* 统一管理用户 redis key
*/
public static UserRedisKey USER_TOKEN = new UserRedisKey(3600, "token");
public static UserRedisKey USER = new UserRedisKey(0, "user");
}