架构系列十(如何自定义封装一套优雅的redis客户端工具)

185 阅读6分钟

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");
}