Jedis API 再封装(单机和集群统一接口)

Jedis API 再封装(单机和集群统一接口)

前言

Redis的效用就不用多说,Jedis作为java版的Redis客户端接口也表现的非常不错。
但是也有其缺陷,比如对外统一接口的数据存储面向的是字符串值,这对我们平时面
向对象开发还是多有不便;spring-redis-data针对Jedis也做了一层封装,但是其
由于固有的风格限制,接口设计过于细致,针对不同实体需要定义不同的template实
例。以上是笔者的粗略见解,所以在大致研究了spring-redis-data和jedis源码的
上才有了大胆的对其进行再封装的想法。

目标

   1、数据交互面向对象,通过泛型实现

   2、对象与字节间的转化自定义多样化,默认JDK的序列化,后期支持JSON格式

   3、单机和集群对外统一接口,只是配置不同而已,集群配置优先级更高


   目标1与目标2是共通的,解决对象与字节间的转换也就解决了使接口面向对象从
   而更方便我们操作,而对象的序列化则是java原生就支持的,但是考虑到序列化
   数据量大有时会有效率方面的问题(摘抄自网上说法),所以JSON格式或许是个
   好的解决方案,所以后期会考虑支持。

   至于目标3,Jedis API中Jedis 与JedisCluster就是实现了统一接口JedisCommands
   笔者最初有这个再封装的想法根源即在此,虽然JedisCommands相较于其子类实现少
   了很多特性,但是基本以及复杂数据操作已全部囊括,其余一些运维类命令,
   通过连接服务器敲命令行解决笔者到觉得更好。

实现

  1、接口定义

  在spring-redis-data中根据redis提供的命令操作的数据类型进行接口的分类定义,
  所以笔者也延续的这个风格

 (篇幅有限仅列举部分接口定义)

RedisStringCommands

public interface RedisStringCommands {

    <T> T get(String key, Class<T> type);

    <T> String set(String key, T value);

    <T> Boolean setNX(String key, T value);

    Long incr(String key);

    ....
}

RedisListCommands

public interface RedisListCommands {

    <T> Long rPush(String key, T... values);

    <T> List<T> lRange(String key, long begin, long end, Class<T> type);

    ....
}

RedisCommands 对外统一接口

public interface RedisCommands extends RedisStringCommands, RedisListCommands {
}
 2、数据转化

 定义一个数据转化接口,专用于对象与字符串间的转化

public interface ValueConverter {

    /**
     * 将redis存储的字符串值转化为对象
     *
     * @param data
     * @param type
     * @param <T>
     * @return 当转化出现异常返回null
     */
    <T> T toValue(String data, Class<T> type);

    /**
     * 将对象转化为字符串值便于redis存储
     *
     * @param value
     * @param <T>
     * @return 当转化出现异常返回null
     */
    <T> String toString(T value);
}
    通过接口定义可知,toValue方法将存储于redis的字节数据转为具体对象,toString
    方法用于将具体对象转化为字符串从而与Jedis接口交互,朋友们可能已经发现,当前
    接口定义只适用于普通对象互转,对于复杂的数据,例如List, Set这类支持泛型类数据
    无法进行互转,受限于实力,目前想到的解决方案对整体框架而言显得格格不入,甚是暴力。
    所以希望广大读者朋友能提供宝贵建议。

   通过java序列化实现该接口
public class SerializableValueConverter implements ValueConverter {
    private static final Logger logger = LoggerFactory.getLogger(SerializableValueConverter.class);

    public static final String DEFT_ENCODING = "ISO-8859-1";

    @Override
    public <T> T toValue(String data, Class<T> type) {
        ObjectInputStream ois = null;
        ByteArrayInputStream bais = null;
        try {
            if (StringUtils.isNotBlank(data)) {
                bais = new ByteArrayInputStream(data.getBytes(DEFT_ENCODING));
                ois = new ObjectInputStream(bais);
                return (T) ois.readObject();
            }
        } catch (Exception e) {
            logger.error("convert string to value occur an ERROR", e);
        } finally {
            closeInStream(ois, bais);
        }
        return null;
    }

    @Override
    public <T> String toString(T value) {
        ObjectOutputStream oos = null;
        ByteArrayOutputStream baos = null;
        try {
            if (value != null) {
                baos = new ByteArrayOutputStream();
                oos = new ObjectOutputStream(baos);
                oos.writeObject(value);
                return new String(baos.toByteArray(), DEFT_ENCODING);
            }
        } catch (Exception e) {
            logger.error("convert value to string occur an ERROR", e);
        } finally {
            closeOutStream(oos, baos);
        }
        return null;
    }

    private void closeOutStream(ObjectOutputStream oos, ByteArrayOutputStream baos) {
        try {
            oos.close();
            baos.close();
        } catch (Exception e) {
            // ignore
        }
    }

    private void closeInStream(ObjectInputStream ois, ByteArrayInputStream bais) {
        try {
            ois.close();
            bais.close();
        } catch (Exception e) {
            // ignore
        }
    }
}
   3、骨架实现

   同大多数资源连接操作处理类似,redis也需要打开连接,执行命令,关闭连接。流程固定,
   针对此类逻辑代码我们需要提供模板方法从而固定流程,所以在骨架实现中笔者引入了一个
   RedisExecutor的抽象类进行该流程的处理,具体定义如下
public abstract class RedisExecutor<T> {

    // Jedis统一接口
    private final JedisCommands connection;

    public RedisExecutor(JedisCommands connection) {
        this.connection = connection;
    }

    // 抽象方法,具体命令执行在此方法中实现
    public abstract T execute(JedisCommands connection, String key);

    // 运行整个流程
    public T run(String key) {
        try {
            return execute(connection, key);
        } catch (Exception e) {
            return null;
        } finally {
            // 释放连接
            releaseConnection(connection);
        }
    }

    // 连接释放处理,比较粗暴
    private void releaseConnection(JedisCommands connection) {
        if (connection instanceof Jedis) {
            ((Jedis) connection).close();
        } else if (connection instanceof JedisCluster) {
            try {
                ((JedisCluster) connection).close();
            } catch (Exception e) {

            }
        }
    }
}
    RedisExecutor正如其名,处理整个操作流程的执行器,run方法固定流程,
    execute方法抽象交由子类实现,调用具体的redis命令

接口的骨架实现类AbstractJedisConnection

public abstract class AbstractJedisConnection implements RedisCommands {

    protected ValueConverter valueConverter;

    public abstract JedisCommands getConnection();

    public AbstractJedisConnection(ValueConverter valueConverter) {
        this.valueConverter = valueConverter;
    }

    @Override
    public <T> T get(String key, final Class<T> type) {
        return new RedisExecutor<T>(getConnection()) {
            @Override
            public T execute(JedisCommands connection, String key) {
                String data = connection.get(key);
                return valueConverter.toValue(data, type);
            }
        }.run(key);
    }

    @Override
    public <T> String set(String key, final T value) {
        return new RedisExecutor<String>(getConnection()) {
            @Override
            public String execute(JedisCommands connection, String key) {
                System.out.println("set key:" + key);
                return connection.set(key, valueConverter.toString(value));
            }
        }.run(key);
    }

}
    AbstractJedisConnection首先需要ValueConverter实例来作为数据转化器,
    其次定义了一个获取Jedis统一接口的方法,该方法应该由具体子类覆盖并返回
    具体的Jedis连接对象是单机Jedis或集群JedisCluster。代码示例中给出了最
    为常见的方法实现,get和set。在get方法中new了一个RedisExecutor对象,
    覆盖了其execute方法,在其中使用JedisCommands调用get接口,再用
    ValueConvert将其返回值转化为具体的对象。set方法类似,只不过调用接口
    不同和转化数据方向相反而已。


  4、单机Jedis和集群JedisCluser实现

  这里的实现就比较简单了,Pool对象由Jedis原生支持,拿来用非常方便
public class JRedisConnection extends AbstractJedisConnection {
    private final Pool<Jedis> pool;

    public JRedisConnection(Pool<Jedis> pool, ValueConverter valueConverter) {
        super(valueConverter);
        this.pool = pool;
    }

    @Override
    public JedisCommands getConnection() {
        if (pool != null) {
            return pool.getResource();
        } else {
            throw new IllegalStateException();
        }
    }
}
public class JRedisClusterConnection extends AbstractJedisConnection {

    private final JedisCluster jedisCluster;

    public JRedisClusterConnection(JedisCluster jedisCluster, ValueConverter valueConverter) {
        super(valueConverter);
        this.jedisCluster = jedisCluster;
    }

    @Override
    public JedisCommands getConnection() {
        return jedisCluster;
    }
}
    至此,笔者对于Jedis API的简单再封装就结束了,能力有限,简单封装,
    仅作为自嗨,用于生产仍需打磨。还有关于键值唯一性保证
    两种模式:
        业务模式,app:module:bizcode:data;
        表格模式,app:module:table:primarykey:[colnum])
    ,以及RedisCommands对象实例的获取(定义对象工厂,通过配置生成具体
    对象并返回,方便集成spring)都没有说明,这些比较简单就不做说明。



  最后欢迎大家进群讨论(Java博客党 313145288)
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值