spring整合redis客戶端及緩存接口設計
來自: http://www.cnblogs.com/xumanbu/p/5160765.html
一、寫在前面
緩存作為系統性能優化的一大殺手锏,幾乎在每個系統或多或少的用到緩存。有的使用本地內存作為緩存,有的使用本地硬盤作為緩存,有的使用緩存服務器。但是無論使用哪種緩存,接口中的方法都是差不多。筆者最近的項目使用的是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);
}</pre>
這個接口用起來總有一些別扭,我總結了一下:
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);
}</pre>
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.jedis.JedisPool;
3 import redis.clients.jedis.JedisPoolConfig;
4 import redis.clients.util.SafeEncoder;
5
6 import com.cxypub.baseframework.sdk.util.ObjectUtils;
7
8 /**
9 * @ClassName: RedisCacheClient
10 * @Description: redis緩存客戶端
11 * @author 徐飛
12 * @date 2015-4-16 上午10:42:32
13 *
14 */
15 public class RedisCacheClient implements SimpleCache {
16
17 private JedisTemple jedisTemple;
18
19 public RedisCacheClient(JedisPoolConfig config, String host, Integer port) {
20 this.initialPool(config, host, port);
21 }
22
23 /**
24 * 初始化非切片池
25 */
26 private void initialPool(JedisPoolConfig config, String host, Integer port) {
27 JedisPool jedisPool = new JedisPool(config, host, port);
28 this.jedisTemple = new JedisTemple(jedisPool);
29 }
30
31 @Override
32 public boolean add(final String key, final Object valueObject) {
33 try {
34 jedisTemple.execute(new RedisPoolCallback<Boolean>() {
35 @Override
36 public Boolean doInJedis(Jedis jedis) {
37 jedis.set(SafeEncoder.encode(key), ObjectUtils.object2Byte(valueObject));
38 return true;
39 }
40
41 });
42 } catch (Exception e) {
43 e.printStackTrace();
44 return false;
45 }
46 return true;
47 }
48
49 @Override
50 public Object get(final String key) {
51
52 return jedisTemple.execute(new RedisPoolCallback<Object>() {
53 @Override
54 public Object doInJedis(Jedis jedis) {
55 byte[] cacheValue = jedis.get(SafeEncoder.encode(key));
56 if (cacheValue != null) {
57 return ObjectUtils.byte2Object(cacheValue);
58 }
59 return null;
60 }
61
62 });
63 }
64
65 @Override
66 public long delete(final String key) {
67 return jedisTemple.execute(new RedisPoolCallback<Long>() {
68 @Override
69 public Long doInJedis(Jedis jedis) {
70 return jedis.del(key);
71 }
72 });
73 }
74
75 @Override
76 public boolean add(final String key, Object value, final int seconds) {
77 try {
78 this.add(key, value);
79 jedisTemple.execute(new RedisPoolCallback<Long>() {
80 @Override
81 public Long doInJedis(Jedis jedis) {
82 return jedis.expire(key, seconds);
83 }
84 });
85 } catch (Exception e) {
86 e.printStackTrace();
87 return false;
88 }
89 return true;
90 }
91
92 @Override
93 public boolean exists(final String key) {
94 return jedisTemple.execute(new RedisPoolCallback<Boolean>() {
95 @Override
96 public Boolean doInJedis(Jedis jedis) {
97 return jedis.exists(key);
98 }
99 });
100 }
101
102 }
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 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<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> <bean id="jedisClient" class="com.cxypub.baseframework.sdk.cache.RedisCacheClient"> <constructor-arg ref="jedisPoolConfig" /> <constructor-arg value="${redis.host}" /> <constructor-arg value="${redis.port}" type="java.lang.Integer" /> </bean></beans></pre>
6、這樣在項目中就可以將jedisClient 注入到任何類中了,我這里寫了一個測試客戶端,這個直接運行的,一同貼上。
1 public class RedisTest { 2 public static void main(String[] args) { 3 JedisPoolConfig config = new JedisPoolConfig(); 4 config.setMaxTotal(500); 5 config.setMaxIdle(5); 6 config.setMaxWaitMillis(1000 * 100); 7 config.setTestOnBorrow(true); 8 RedisCacheClient client = new RedisCacheClient(config, "192.168.1.215", 6379); 9 Dictionary dict = new Dictionary(); 10 dict.setId("qwertryruyrtutyu"); 11 dict.setDictChineseName("上海"); 12 dict.setCreateTime(new Date()); 13 client.add("xufei", dict); 14 Dictionary dict2 = (Dictionary) client.get("xufei"); 15 System.out.println(dict2); 16 System.out.println(dict == dict2); 17 } 18 }</div>