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


    一、写在前面

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

    二、一些想法

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    /**
    * @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。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    /**
    * @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);
        }
    }
    复制代码

    http://www.cnblogs.com/xumanbu/p/5160765.html

  • 相关阅读:
    渗透学习——资料整理
    c语言——运算符、分支结构、循环结构
    c语言——字符串变量、函数
    总结Java开发者经常会犯的前十种错误
    项目开发遇到的问题及其解决.总结
    Java开发者写SQL时常犯的10个错误
    Java开发中常见的异常问题
    Java编程:常见问题汇总
    Exploded location overlaps an existing deployment解决办法
    Eclipse下svn的创建分支/合并/切换使用
  • 原文地址:https://www.cnblogs.com/softidea/p/5161475.html
Copyright © 2020-2023  润新知