首先不得不服Spring這個宇宙無敵的開源框架,幾乎整合了所有流行的其它框架,http://projects.spring.io/spring-data/ 從這上面看,當下流行的redis、solr、hadoop、mongoDB、couchBase... 全都收入囊中。對于redis整合而言,主要用到的是spring-data-redis
使用步驟:
一、pom添加依賴項
<dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId> <version>1.4.1.RELEASE</version> </dependency>
其它Spring必備組件,比如Core,Beans之類,大家自行添加吧
觀察一下:
jedis、jredis等常用java的redis client已經支持了,不知道以后會不會集成Redisson,spring-data-redis提供了一個非常有用的類:StringRedisTemplate
對于大多數緩存應用場景而言,字符串是最常用的緩存項,用StringRedisTemplate可以輕松應付。
二、spring配置

1 <bean id="redisSentinelConfiguration" 2 class="org.springframework.data.redis.connection.RedisSentinelConfiguration"> 3 <property name="master"> 4 <bean class="org.springframework.data.redis.connection.RedisNode"> 5 <property name="name" value="mymaster"></property> 6 </bean> 7 </property> 8 <property name="sentinels"> 9 <set> 10 <bean class="org.springframework.data.redis.connection.RedisNode"> 11 <constructor-arg index="0" value="10.6.1**.**5" /> 12 <constructor-arg index="1" value="7031" /> 13 </bean> 14 <bean class="org.springframework.data.redis.connection.RedisNode"> 15 <constructor-arg index="0" value="10.6.1**.**6" /> 16 <constructor-arg index="1" value="7031" /> 17 </bean> 18 <bean class="org.springframework.data.redis.connection.RedisNode"> 19 <constructor-arg index="0" value="10.6.1**.**1" /> 20 <constructor-arg index="1" value="7031" /> 21 </bean> 22 </set> 23 </property> 24 </bean> 25 26 <bean id="jedisConnFactory" 27 class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"> 28 <constructor-arg ref="redisSentinelConfiguration" /> 29 </bean> 30 31 <bean id="stringRedisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate"> 32 <property name="connectionFactory" ref="jedisConnFactory" /> 33 </bean>
提示:上面配置中的端口為sentinel的端口,而非redis-server的端口。
這里我們使用Sentinel模式來配置redis連接,從上篇學習知道,sentinel是一種高可用架構,個人推薦在生產環境中使用sentinel模式。
注:26-28行,經試驗,如果修改了默認端口,這里必須明細指定hostName及port,否則運行后,無法正確讀寫緩存,參考下面的配置:
(2016-4-2更新:最新1.6.4版的spring-data-redis 已經修正了這個問題,無需再指定端口和hostname)
<bean id="jedisConnFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"> <property name="hostName" value="10.6.53.xxx"/> <property name="port" value="8830"/> <property name="usePool" value="false"/> <constructor-arg ref="redisSentinelConfiguration"/> </bean>
其中hostName為當前master的IP,port為redis-server的運行端口(非sentinel端口),此外還要設置usePool為false,由于sentinel可能會自行切換master節點,如果不清楚當前的master節點是哪臺機器,可以用前面提到的命令./redis-cli -p <sentinal端口號> sentinel masters查看,或者用java代碼輸出,參考下面的代碼:
1 ApplicationContext ctx = new FileSystemXmlApplicationContext("/opt/app/spring-redis.xml"); 2 StringRedisTemplate template = ctx.getBean(StringRedisTemplate.class); 3 for (RedisServer m : template.getConnectionFactory().getSentinelConnection().masters()) { 4 logger.debug(m); 5 }
另外<property name="usePool" value="false"/> 這里的value值如果改成true,經實際測試,發現偶爾會報如下錯誤(如果報錯,換成false通常就可以了):
redis.clients.jedis.exceptions.JedisDataException: ERR unknown command 'SET'
其它注意事項:
配置文件中的sentinels屬性的Set 中的節點,并非一定要在同一個master下,也可以是歸屬于多個master,即:如果這里配置了10個node信息,其中1-3歸屬于master1,剩下的4-10屬于master2,這也是允許的。
這樣調用時,通過StringRedisTemplate.getConnectionFactory().getSentinelConnection().masters()可以返回一個master的列表,然后代碼中根據需要,向某一個需要的master寫入緩存.
三、單元測試

1 @Test 2 public void testSpringRedis() { 3 ConfigurableApplicationContext ctx = null; 4 try { 5 ctx = new ClassPathXmlApplicationContext("spring.xml"); 6 7 StringRedisTemplate stringRedisTemplate = ctx.getBean("stringRedisTemplate", StringRedisTemplate.class); 8 9 // String讀寫 10 stringRedisTemplate.delete("myStr"); 11 stringRedisTemplate.opsForValue().set("myStr", "http://yjmyzz.cnblogs.com/"); 12 System.out.println(stringRedisTemplate.opsForValue().get("myStr")); 13 System.out.println("---------------"); 14 15 // List讀寫 16 stringRedisTemplate.delete("myList"); 17 stringRedisTemplate.opsForList().rightPush("myList", "A"); 18 stringRedisTemplate.opsForList().rightPush("myList", "B"); 19 stringRedisTemplate.opsForList().leftPush("myList", "0"); 20 List<String> listCache = stringRedisTemplate.opsForList().range( 21 "myList", 0, -1); 22 for (String s : listCache) { 23 System.out.println(s); 24 } 25 System.out.println("---------------"); 26 27 // Set讀寫 28 stringRedisTemplate.delete("mySet"); 29 stringRedisTemplate.opsForSet().add("mySet", "A"); 30 stringRedisTemplate.opsForSet().add("mySet", "B"); 31 stringRedisTemplate.opsForSet().add("mySet", "C"); 32 Set<String> setCache = stringRedisTemplate.opsForSet().members( 33 "mySet"); 34 for (String s : setCache) { 35 System.out.println(s); 36 } 37 System.out.println("---------------"); 38 39 // Hash讀寫 40 stringRedisTemplate.delete("myHash"); 41 stringRedisTemplate.opsForHash().put("myHash", "PEK", "北京"); 42 stringRedisTemplate.opsForHash().put("myHash", "SHA", "上海虹橋"); 43 stringRedisTemplate.opsForHash().put("myHash", "PVG", "浦東"); 44 Map<Object, Object> hashCache = stringRedisTemplate.opsForHash() 45 .entries("myHash"); 46 for (Map.Entry<Object, Object> entry : hashCache.entrySet()) { 47 System.out.println(entry.getKey() + " - " + entry.getValue()); 48 } 49 50 System.out.println("---------------"); 51 52 } finally { 53 if (ctx != null && ctx.isActive()) { 54 ctx.close(); 55 } 56 } 57 58 }
運行一下,行云流水般的輸出:
...
信息: Created JedisPool to master at 10.6.144.***:7030
http://yjmyzz.cnblogs.com/
---------------
0
A
B
---------------
C
B
A
---------------
SHA - 上海虹橋
PVG - 浦東
PEK - 北京
---------------
...
注意紅色標出部分,從eclipse控制臺的輸出,還能看出當前的master是哪臺服務器
這里再補充一點小技巧:如果想遍歷所有master及slave可以參考以下代碼

1 @Test 2 public void testGetAllMasterAndSlaves() { 3 ApplicationContext ctx = new FileSystemXmlApplicationContext("D:/spring-redis.xml"); 4 StringRedisTemplate template = ctx.getBean(StringRedisTemplate.class); 5 RedisSentinelConnection conn = template.getConnectionFactory().getSentinelConnection(); 6 for (RedisServer m : conn.masters()) { 7 System.out.println("master => " + m);//打印master信息 8 Collection<RedisServer> slaves = conn.slaves(m); 9 //打印該master下的所有slave信息 10 for (RedisServer s : slaves) { 11 System.out.println("slaves of " + m + " => " + s); 12 } 13 System.out.println("--------------"); 14 } 15 ((FileSystemXmlApplicationContext) ctx).close(); 16 }
輸出類似下面的結果:
master => 172.20.16.191:6379
slaves of 172.20.16.191:6379 => 172.20.16.192:6379
注:這里輸出的slaves列表,經實際測試,發現只是根據redis server端的配置呆板的返回slave node列表,不管這些node是死是活,換句話說,就算某個slave已經down掉,這里依然會返回。
三、POJO對象的緩存
Spring提供的StringRedisTemplate只能對String操作,大多數情況下已經夠用,但如果真需要向redis中存放POJO對象也不難,我們可以參考StringRedisTemplate的源碼,擴展出ObjectRedisTemplate

1 package org.springframework.data.redis.core; 2 3 import org.springframework.data.redis.connection.DefaultStringRedisConnection; 4 import org.springframework.data.redis.connection.RedisConnection; 5 import org.springframework.data.redis.connection.RedisConnectionFactory; 6 import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; 7 import org.springframework.data.redis.serializer.RedisSerializer; 8 9 public class ObjectRedisTemplate<T> extends RedisTemplate<String, T> { 10 11 public ObjectRedisTemplate(RedisConnectionFactory connectionFactory, 12 Class<T> clazz) { 13 14 RedisSerializer<T> objectSerializer = new Jackson2JsonRedisSerializer<T>( 15 clazz); 16 17 RedisSerializer<String> objectKeySerializer = new Jackson2JsonRedisSerializer<String>( 18 String.class); 19 20 setKeySerializer(objectKeySerializer); 21 setValueSerializer(objectSerializer); 22 setHashKeySerializer(objectSerializer); 23 setHashValueSerializer(objectSerializer); 24 25 setConnectionFactory(connectionFactory); 26 afterPropertiesSet(); 27 } 28 29 protected RedisConnection preProcessConnection(RedisConnection connection, 30 boolean existingConnection) { 31 return new DefaultStringRedisConnection(connection); 32 } 33 }
然后就可以這樣用了:

1 @Test 2 public void testSpringRedis() { 3 ConfigurableApplicationContext ctx = null; 4 try { 5 ctx = new ClassPathXmlApplicationContext("spring.xml"); 6 7 JedisConnectionFactory connFactory = ctx.getBean( 8 "jedisConnFactory", JedisConnectionFactory.class); 9 10 ObjectRedisTemplate<SampleBean> template = new ObjectRedisTemplate<SampleBean>( 11 connFactory, SampleBean.class); 12 13 template.delete("myBean"); 14 SampleBean bean = new SampleBean("菩提樹下的楊過"); 15 template.opsForValue().set("myBean", bean); 16 17 System.out.println(template.opsForValue().get("myBean")); 18 19 } finally { 20 if (ctx != null && ctx.isActive()) { 21 ctx.close(); 22 } 23 } 24 }
其中SampleBean的定義如下:

1 package com.cnblogs.yjmyzz; 2 3 import java.io.Serializable; 4 5 public class SampleBean implements Serializable { 6 7 private static final long serialVersionUID = -303232410998377570L; 8 9 private String name; 10 11 public SampleBean() { 12 } 13 14 public SampleBean(String name) { 15 this.name = name; 16 } 17 18 public String getName() { 19 return name; 20 } 21 22 public void setName(String name) { 23 this.name = name; 24 } 25 26 public String toString() { 27 return "name:" + name; 28 } 29 30 }
注:由于不是標準的String類型,所以在redis控制臺,用./redis-cli get myBean是看不到緩存內容的,只能得到nil的輸出,不要誤以為set沒成功!通過代碼是可以正常get到緩存值的。
另外關于POJO對象的緩存,還有二個注意事項:
a) POJO類必須要有默認的無參構造函數,否則反序列化時會報錯
b) ObjectRedisTemplate<T>中的T不能是接口,比如 DomainModelA繼承自接口 IModelA,使用ObjectRedisTemplate時,要寫成ObjectRedisTemplate<DomainModelA>而不是ObjectRedisTemplate<IModelA>,否則反序列化時也會出錯
文章列表