• spring整合redis客户端及缓存接口设计


    一、写在前面

    缓存作为系统性能优化的一大杀手锏,几乎在每个系统或多或少的用到缓存。有的使用本地内存作为缓存,有的使用本地硬盘作为缓存,有的使用缓存服务器。但是无论使用哪种缓存,接口中的方法都是差不多。笔者最近的项目使用的是memcached作为缓存服务器,由于memcached的一些限制,现在想换redis作为缓存服务器。思路就是把memached的客户端换成redis客户端,接口依然是原来的接口,这样对系统可以无损替换,接口不变,功能不变,只是客户端变了。本文不介绍缓存的用法,不介绍redis使用方法,不介绍memcached与redis有何区别。只是实现一个redis客户端,用了jedis作为第三方连接工具。

    二、一些想法

    首先贴一下现项目中同事编写的缓存接口:

    /**
     * @ClassName: DispersedCachClient
     * @Description: 分布式缓存接口,每个方法:key最大长度128字符,valueObject最大1Mb,默认超时时间30天
     * @date 2015-4-14 上午11:51:18
     *
     */
    public interface DispersedCachClient {
    	
    	
    	/**
    	 * add(要设置缓存中的对象(value),)
    	 *
    	 * @Title: add
    	 * @Description: 要设置缓存中的对象(value),如果没有则插入,有就不操作。
    	 * @param key	键
    	 * @param valueObject	缓存对象
    	 * @return  Boolean true 成功,false 失败
    	 */
    	public Boolean add(String key, Object valueObject);
    	
    	/**
    	 * add(要设置缓存中的对象(value),指定保存有效时长)
    	 *
    	 * @Title: add
    	 * @Description: 要设置缓存中的对象(value),指定有效时长,如果没有则插入,有就不操作。
    	 * @param key	键
    	 * @param valuObject	缓存对象
    	 * @param keepTimeInteger	有效时长(秒)
    	 * @return  Boolean true 成功,false 失败
    	 */
    	public Boolean add(String key, Object valueObject, Integer keepTimeInteger);
    	
    	/**
    	 * 
    	 * add(要设置缓存中的对象(value),指定有效时间点。)
    	 *
    	 * @Title: add
    	 * @Description: 要设置缓存中的对象(value),指定有效时间点,如果没有则插入,有就不操作。
    	 * @date 2015-4-14 上午11:58:12
    	 * @param key	键
    	 * @param valuObject	缓存对象
    	 * @param keepDate	时间点
    	 * @return  Boolean true 成功,false 失败
    	 */
    	public Boolean add(String key, Object valueObject, Date keepDate);
    	
    	/**
    	 * 
    	 * set(要设置缓存中的对象(value),)
    	 *
    	 * @Title: set
    	 * @Description: 如果没有则插入,如果有则修改
    	 * @date 2015-4-14 下午01:44:22
    	 * @param key	键
    	 * @param valueObject	缓存对象
    	 * @return  Boolean true 成功,false 失败	
    	 */
    	public Boolean set(String key,Object valueObject) ;
    	
    	/**
    	 * 
    	 * set(要设置缓存中的对象(value),指定有效时长)
    	 *
    	 * @Title: set
    	 * @Description: 指定有效时长,如果没有则插入,如果有则修改
    	 * @date 2015-4-14 下午01:45:22
    	 * @param key	键
    	 * @param valueObject	缓存对象
    	 * @param keepTimeInteger	保存时长(秒)
    	 * @return  Boolean true 成功,false 失败
    	 */
    	public Boolean set(String key, Object valueObject, Integer keepTimeInteger);
    	
    	/**
    	 * 
    	 * set(要设置缓存中的对象(value),指定有效时间点)
    	 *
    	 * @Title: set
    	 * @Description: 指定有效时间点,如果没有则插入,如果有则修改
    	 * @date 2015-4-14 下午01:45:55
    	 * @param key	键
    	 * @param valueObject	缓存对象
    	 * @param keepDate	有效时间点
    	 * @return  Boolean true 成功,false 失败
    	 */
    	public Boolean set(String key, Object valueObject, Date keepDate);
    	
    	/**
    	 * 
    	 * replace(要设置缓存中的对象(value),有效)
    	 *
    	 * @Title: replace
    	 * @Description: 有效,如果没有则不操作,如果有则修改
    	 * @date 2015-4-14 下午01:47:04
    	 * @param key	键
    	 * @param valueObject	缓存对象
    	  * @return  Boolean true 成功,false 失败  
    	 */
    	public Boolean replace(String key,Object valueObject) ;
    	
    	/**
    	 * 
    	 * replace(要设置缓存中的对象(value),指定有效时长)
    	 *
    	 * @Title: replace
    	 * @Description: 指定有效时长,如果没有则不操作,如果有则修改
    	 * @date 2015-4-14 下午01:47:30
    	 * @param key	键
    	 * @param valueObject	缓存对象
    	 * @param keepTimeInteger	缓存时长(秒)
    	  * @return  Boolean true 成功,false 失败  
    	 */
    	public Boolean replace(String key, Object valueObject, Integer keepTimeInteger);
    	
    	/**
    	 * 
    	 * replace(要设置缓存中的对象(value),指定有效时间点)
    	 *
    	 * @Title: replace
    	 * @Description: 指定有效时间点,如果没有则不操作,如果有则修改
    	 * @date 2015-4-14 下午01:48:09
    	 * @param key	键值对
    	 * @param valueObject	缓存对象
    	 * @param keepDate	有效时间点
    	 * @return  Boolean true 成功,false 失败  
    	 */
    	public Boolean replace(String key, Object valueObject, Date keepDate);
    	
    	/**
    	 * 
    	 * get(获得一个缓存对象)
    	 *
    	 * @Title: get
    	 * @Description: 获得一个缓存对象,响应超时时间默认
    	 * @date 2015-4-14 下午04:18:16
    	 * @param key	键
    	 * @return  Obeject  
    	 */
    	public Object get( String key );
    	
    	/**
    	 * 
    	 * getMulti(获得Map形式的多个缓存对象)
    	 *
    	 * @Title: getMulti
    	 * @Description: 获得Map形式的多个缓存对象,响应超时时间默认
    	 * @date 2015-4-14 下午04:53:07
    	 * @param keys	键存入的string[]
    	 * return  Map<String,Object>  
    	 */
    	public Map<String,Object> getMulti( List<String> keys );
    	
    	/**
    	 * 
    	 * gets(获得一个带版本号的缓存对象)
    	 *
    	 * @Title: gets
    	 * @Description: 获得一个带版本号的缓存对象
    	 * @date 2015-4-16 上午09:15:57
    	 * @param key	键
    	 * @return  Object
    	 */
    	public Object gets(String key);
    	
    	/**
    	 * 
    	 * getMultiArray(获得数组形式的多个缓存对象)
    	 *
    	 * @Title: getMultiArray
    	 * @Description: 获得数组形式的多个缓存对象
    	 * @date 2015-4-16 上午09:27:29
    	 * @param keys	键存入的string[]
    	 * @return Object[]
    	 * @throws
    	 */
    	public Object[] getMultiArray( List<String> keys );
    	
    	/**
    	 * 
    	 * cas(带版本号存缓存,与gets配合使用)
    	 *
    	 * @Title: cas
    	 * @Description: 带版本号存缓存,与gets配合使用,超时时间默认
    	 * @date 2015-4-16 上午09:53:39
    	 * @param key	键
    	 * @param valueObject	缓存对象
    	 * @param versionNo		版本号
    	 * @return  Boolean true 成功,false 失败  
    	 */
    	public boolean cas(String key, Object valueObject, long versionNo);
    	
    	
    	/** cas(带版本号存缓存,与gets配合使用)
    	 *
    	 * @Title: cas
    	 * @Description: 带版本号存缓存,与gets配合使用,指定超时时长
    	 * @date 2015-4-16 上午09:58:06
    	 * @param key	键
    	 * @param valueObject	缓存对象
    	 * @param keepTimeInteger	超时时长
    	 * @param versionNo		版本号
    	 * @return  Boolean true 成功,false 失败
    	 */
    	public boolean cas(String key, Object valueObject, Integer keepTimeInteger, long versionNo);
    	
    	/**
    	 * 
    	 * cas(带版本号存缓存,与gets配合使用)
    	 *
    	 * @Title: cas
    	 * @Description: 带版本号存缓存,与gets配合使用,指定超时时间点
    	 * @date 2015-4-16 上午10:02:38
    	 * @param key	键
    	 * @param valueObject	缓存对象
    	 * @param keepTime	超时时间点
    	 * @param versionNo		版本号
    	 * @return  Boolean true 成功,false 失败  
    	 */
    	public boolean cas(String key, Object valueObject, Date keepDate, long versionNo);
    	/**
    	 * 
    	 * delete(删除缓存)
    	 *
    	 * @Title: delete
    	 * @Description: 删除缓存
    	 * @date 2015-4-16 上午11:20:13
    	 * @param key	键
    	 */
    	public boolean delete(String key);
    	
    }
    

    这个接口用起来总有一些别扭,我总结了一下:

    1、接口名称命名为DispersedCachClient 这个命名含义是分布式缓存客户端(cache少了一个字母),其实这个接口跟分布式一点关系都没有,其实就是缓存接口;

    2、接口方法太多了,实际在项目中并没有方法使用率只有20%左右,所有有精简的必要;

    3、这个接口很多方法设计是套用memcached客户端设计的,也就是说换成redis后会不通用。

    这里没有说这个接口写的不好,而是说还有优化的空间,其次也给自己提个醒,在设计一些使用公共的接口时有必要多花些心思,因为一旦设计后,后面改动的可能性比较小。 

    三、代码实现

    使用jedis客户端需要使用连接池,使用连接后需要将连接放回连接池,失效的连接要放到失效的连接池,类似jdbc需要进行连接的处理,为了避免写重复恶心的代码,参照了spring的JdbcTemple模板设计方式。废话没有,直接上代码吧。

    1、重新设计的缓存客户端接口,这个接口就一个特点“简单”,目的是为了做到通用,故命名为SimpleCache。

    /**
     * @ClassName: DistributedCacheClient
     * @Description: 缓存接口
     * @author 徐飞
     * @date 2016年1月26日 上午11:41:27
     *
     */
    public interface SimpleCache {
    
    	/**
    	 * @Title: add
    	 * @Description: 添加一个缓冲数据
    	 * @param key 字符串的缓存key
    	 * @param value 缓冲的缓存数据
    	 * @return
    	 * @author 徐飞
    	 */
    	boolean add(String key, Object value);
    
    	/**
    	 * @Title: add
    	 * @Description: 缓存一个数据,并指定缓存过期时间
    	 * @param key
    	 * @param value
    	 * @param seconds
    	 * @return
    	 * @author 徐飞
    	 */
    	boolean add(String key, Object value, int seconds);
    
    	/**
    	 * @Title: get
    	 * @Description: 根据key获取到一直值
    	 * @param key 字符串的缓存key
    	 * @return
    	 * @author 徐飞
    	 */
    	Object get(String key);
    
    	/**
    	 * @Title: delete
    	 * @Description: 删除一个数据问题
    	 * @param key 字符串的缓存key
    	 * @return
    	 * @author 徐飞
    	 */
    	long delete(String key);
    
    	/**
    	 * @Title: exists
    	 * @Description: 判断指定key是否在缓存中已经存在
    	 * @param key 字符串的缓存key
    	 * @return
    	 * @author 徐飞
    	 */
    	boolean exists(String key);
    
    }
    

      

    2、JedisTemple :Jedis 操作模板类,请参照Spring的JdbcTemple封装重复但又必要的操作

     1 /**
     2  * @ClassName: JedisTemple
     3  * @Description: Jedis 操作模板类,为啥要这个?请参照{@link JdbcTemple} 封装重复不必要的操作
     4  * @author 徐飞
     5  * @date 2016年1月26日 下午2:37:24
     6  *
     7  */
     8 public class JedisTemple {
     9 
    10     /** 缓存客户端 **/
    11     private JedisPool jedisPool;// 非切片连接池
    12 
    13     public JedisTemple(JedisPool jedisPool) {
    14         this.jedisPool = jedisPool;
    15     }
    16 
    17     /**
    18      * @Title: execute
    19      * @Description: 执行{@link RedisPoolCallback#doInJedis(Jedis)}的方法
    20      * @param action
    21      * @return
    22      * @author 徐飞
    23      */
    24     public <T> T execute(RedisPoolCallback<T> action) {
    25         T value = null;
    26         Jedis jedis = null;
    27         try {
    28             jedis = jedisPool.getResource();
    29             return action.doInJedis(jedis);
    30         } catch (Exception e) {
    31             // 释放redis对象
    32             jedisPool.returnBrokenResource(jedis);
    33             e.printStackTrace();
    34         } finally {
    35             // 返还到连接池
    36             returnResource(jedisPool, jedis);
    37         }
    38 
    39         return value;
    40     }
    41 
    42     /** 
    43     * 返还到连接池 
    44     * @param pool  
    45     * @param redis 
    46     */
    47     private void returnResource(JedisPool pool, Jedis redis) {
    48         // 如果redis为空不返回
    49         if (redis != null) {
    50             pool.returnResource(redis);
    51         }
    52     }
    53 
    54     public JedisPool getJedisPool() {
    55         return jedisPool;
    56     }
    57 
    58     public void setJedisPool(JedisPool jedisPool) {
    59         this.jedisPool = jedisPool;
    60     }
    61 
    62 }

    3、RedisPoolCallback:redis操作回调接口,此接口主要为JedisTemple模板使用

     1 import redis.clients.jedis.Jedis;
     2 
     3 /**
     4  * @ClassName: RedisPoolCallback
     5  * @Description: redis操作回调接口,此接口主要为JedisTemple模板使用
     6  * @author 徐飞
     7  * @date 2016年1月26日 下午2:35:41
     8  *
     9  * @param <T>
    10  */
    11 public interface RedisPoolCallback<T> {
    12     /**
    13      * @Title: doInJedis
    14      * @Description: 回调执行方法,需要重新此方法,一般推荐使用匿名内部类
    15      * @param jedis
    16      * @return
    17      * @author 徐飞
    18      */
    19     T doInJedis(Jedis jedis);
    20 }

    4、RedisCacheClient :redis客户端实现类

     1 import redis.clients.jedis.Jedis;
     2 import redis.clients.util.SafeEncoder;
     3 
     4 import com.cxypub.baseframework.sdk.util.ObjectUtils;
     5 
     6 /**
     7  * @ClassName: RedisCacheClient
     8  * @Description: redis缓存客户端
     9  * @author 徐飞
    10  * @date 2015-4-16 上午10:42:32
    11  *
    12  */
    13 public class RedisCacheClient implements SimpleCache {
    14 
    15     private JedisTemple jedisTemple;
    16 
    17     public RedisCacheClient(JedisTemple jedisTemple) {
    18         this.jedisTemple = jedisTemple;
    19     }
    20 
    21     @Override
    22     public boolean add(final String key, final Object valueObject) {
    23         try {
    24             jedisTemple.execute(new RedisPoolCallback<Boolean>() {
    25                 @Override
    26                 public Boolean doInJedis(Jedis jedis) {
    27                     jedis.set(SafeEncoder.encode(key), ObjectUtils.object2Byte(valueObject));
    28                     return true;
    29                 }
    30 
    31             });
    32         } catch (Exception e) {
    33             e.printStackTrace();
    34             return false;
    35         }
    36         return true;
    37     }
    38 
    39     @Override
    40     public Object get(final String key) {
    41 
    42         return jedisTemple.execute(new RedisPoolCallback<Object>() {
    43             @Override
    44             public Object doInJedis(Jedis jedis) {
    45                 byte[] cacheValue = jedis.get(SafeEncoder.encode(key));
    46                 if (cacheValue != null) {
    47                     return ObjectUtils.byte2Object(cacheValue);
    48                 }
    49                 return null;
    50             }
    51 
    52         });
    53     }
    54 
    55     @Override
    56     public long delete(final String key) {
    57         return jedisTemple.execute(new RedisPoolCallback<Long>() {
    58             @Override
    59             public Long doInJedis(Jedis jedis) {
    60                 return jedis.del(key);
    61             }
    62         });
    63     }
    64 
    65     @Override
    66     public boolean add(final String key, Object value, final int seconds) {
    67         try {
    68             this.add(key, value);
    69             jedisTemple.execute(new RedisPoolCallback<Long>() {
    70                 @Override
    71                 public Long doInJedis(Jedis jedis) {
    72                     return jedis.expire(key, seconds);
    73                 }
    74             });
    75         } catch (Exception e) {
    76             e.printStackTrace();
    77             return false;
    78         }
    79         return true;
    80     }
    81 
    82     @Override
    83     public boolean exists(final String key) {
    84         return jedisTemple.execute(new RedisPoolCallback<Boolean>() {
    85             @Override
    86             public Boolean doInJedis(Jedis jedis) {
    87                 return jedis.exists(key);
    88             }
    89         });
    90     }
    91 
    92 }
    
    

    5、实现了代码,下面就开始将客户端整合进spring就行了,上配置文件

    redis.properties:

     1 # Redis settings
     2 redis.host=192.168.1.215
     3 redis.port=6379
     4 redis.pass=
     5 
     6 # 控制一个pool可分配多少个jedis实例,通过pool.getResource()来获取;
     7 # 如果赋值为-1,则表示不限制;如果pool已经分配了maxActive个jedis实例,则此时pool的状态为exhausted(耗尽)。
     8 redis.maxTotal=600
     9 # 控制一个pool最多有多少个状态为idle(空闲的)的jedis实例。
    10 redis.maxIdle=300
    11 # 表示当borrow(引入)一个jedis实例时,最大的等待时间,如果超过等待时间,则直接抛出JedisConnectionException;
    12 redis.maxWaitMillis=1000
    13 # 在borrow一个jedis实例时,是否提前进行validate操作;如果为true,则得到的jedis实例均是可用的;
    14 redis.testOnBorrow=true

    applicationContext-redis.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
        xmlns:tx="http://www.springframework.org/schema/tx"
        xsi:schemaLocation="http://www.springframework.org/schema/beans 
               http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
               http://www.springframework.org/schema/aop 
               http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
               http://www.springframework.org/schema/tx 
               http://www.springframework.org/schema/tx/spring-tx-2.0.xsd"
        default-autowire="autodetect" default-lazy-init="false">
    
        <!-- jedis 配置 -->
        <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">  
            <property name="maxIdle" value="${redis.maxIdle}" />  
            <property name="maxTotal" value="${redis.maxTotal}" />  
            <property name="maxWaitMillis" value="${redis.maxWaitMillis}" />  
            <property name="testOnBorrow" value="${redis.testOnBorrow}" />  
        </bean>
        
        <!-- jedis 连接池  -->
        <bean id="jedisPool" class="redis.clients.jedis.JedisPool">
            <constructor-arg ref="jedisPoolConfig" />
            <constructor-arg value="${redis.host}" />
            <constructor-arg value="${redis.port}" type="java.lang.Integer" />
        </bean>
        
        <!-- jedis 操作 temple  -->
        <bean id="jedisTemple" class="com.cxypub.baseframework.sdk.cache.JedisTemple">
            <constructor-arg ref="jedisPool" />
        </bean>
        
        <!-- jedis 客户端,真正提供给系统使用的客户端,当然如果这个客户端的方法不满足,可以使用jedisTemple -->
        <bean id="jedisClient" class="com.cxypub.baseframework.sdk.cache.RedisCacheClient">
            <constructor-arg ref="jedisTemple" />
        </bean>
        
    
    </beans>

    6、这样在项目中就可以将jedisClient 注入到任何类中了,我这里写了一个测试客户端,这个直接运行的,一同贴上。

    package com.cxypub.baseframework.sdk.cache;
    
    import java.util.Date;
    
    import redis.clients.jedis.JedisPool;
    import redis.clients.jedis.JedisPoolConfig;
    
    import com.cxypub.baseframework.sdk.dictionary.entity.Dictionary;
    
    public class RedisTest {
        public static void main(String[] args) {
            JedisPoolConfig config = new JedisPoolConfig();
            config.setMaxTotal(500);
            config.setMaxIdle(5);
            config.setMaxWaitMillis(1000 * 100);
            config.setTestOnBorrow(true);
            JedisPool jedisPool = new JedisPool(config, "192.168.1.215", 6379);
            JedisTemple jedisTemple = new JedisTemple(jedisPool);
            RedisCacheClient client = new RedisCacheClient(jedisTemple);
            Dictionary dict = new Dictionary();
            dict.setId("qwertryruyrtutyu");
            dict.setDictChineseName("上海");
            dict.setCreateTime(new Date());
            client.add("xufei", dict);
            Dictionary dict2 = (Dictionary) client.get("xufei");
            System.out.println(dict2);
            System.out.println(dict == dict2);
        }
    }
    作者:徐飞
    出处:www.cnblogs.com/xumanbu/
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
  • 相关阅读:
    core--线程同步(用户模式)
    Android-Kotlin-Activity直接的跳转
    Android-Kotlin-枚举enum
    Android-Kotlin-单例模式
    Android-Kotlin-代理和委托
    Android-Kotlin-接口与多态的表现
    Android-Kotlin-set/get方法的使用
    Android-Kotlin-继承
    Android-Kotlin简单计算器功能
    CentOS 6.5 X64 U盘启动盘制作
  • 原文地址:https://www.cnblogs.com/xumanbu/p/5160765.html
Copyright © 2020-2023  润新知