缓存是改善软件性能的第一手段,大型网站架构设计在很多方面都使用了缓存。将热点数据缓存在内存中,能够减轻数据库负担,并且能够一定程度的加快数据读取速度;将需要重复使用的复杂计算结果缓存在内存中,能够避免再次运算造成的资源浪费。
Redis相对于Memcached而言,虽然都是Key-Value存储结构,但Redis数据类型更为丰富,并且支持排序、允许数据持久化,在功能上更为强大。
1、导包
首先自然是Redis的客户端实现jedis,然后导入Spring-data-redis。
Spring-data-redis提供了在srping应用中通过简单的配置访问redis服务,对reids底层开发包(Jedis、JRedis、RJC)进行了高度封装,并对spring 3.1 cache进行了实现
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>1.8.1.RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.4.2</version>
</dependency>
因为创建Redis线程开销较大,所以这里我们使用可伸缩的线程池commons-poll2来进行管理
2、配置
定义Redis连接信息文件:redis.properties
# 地址
redis.host=127.0.0.1
# 端口
redis.port=6379
# 验证密码
redis.pass=262419
# 最大线程数(默认8)
redis.maxTotal=8
# 最大空闲线程数(默认8)
redis.maxIdle=8
# 获取连接时的最大等待毫秒数(如果设置为阻塞时BlockWhenExhausted),如果超时就抛异常,小于零:阻塞不确定的时间,默认-1
redis.maxWaitMillis=-1
# 在获取连接的时候检查有效性, 默认false
redis.testOnBorrow=true
为了方便管理,我们定义单独的配置文件:spring-redis.xml
首先是定义一个Redis的连接工厂JedisConnectionFactory,并配置Redis连接线程池
spring-redis.xml
<!--
这里需要注意,在使用Redis作为缓存时,可能还会存在一个数据库的jdbc配置文件;Spring在多次使用property-placeholder来引入文件时会报unresolvable异常,此时需要在每个property-placeholder上配置ignore-unresolvable="true"
-->
<!-- 引入redis连接信息文件 -->
<context:property-placeholder location="classpath:redis.properties" ignore-unresolvable="true"/>
<!-- redis连接线程池设置 -->
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig"
p:maxTotal="${redis.maxTotal}"
p:maxIdle="${redis.maxIdle}"
p:maxWaitMillis="${redis.maxWaitMillis}"
p:testOnBorrow="${redis.testOnBorrow}" />
<!-- redis连接工厂,由此获取连接 -->
<bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
p:host-name="${redis.host}"
p:port="${redis.port}"
p:password="${redis.pass}"
p:pool-config-ref="poolConfig" />
我们在实际使用时并不是直接获得连接进行操作,而是通过RedisTemplate模板。RedisTemplate实现自RedisOperations接口,提供了redis各种操作、异常处理及序列化,支持发布订阅
<bean id="redisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">
<property name="connectionFactory" ref="connectionFactory" />
</bean>
当然,别忘了引用Spring-redis.xml。这里引用 Spring-redis.xml 有两种方式:在web.xml文件中配置和在Spring配置文件中引入,其实两者并没什么实际上的区别
(1)在 web.xml 中配置
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-config.xml,classpath:spring-redis.xml</param-value>
</context-param>
(2)在Spring配置文件 spring-config.xml 中import
<import resource="classpath:spring-redis.xml" />
3、使用
使用时可以直接在类中注入RedisTemplate,然后通过RedisTemplate进行操作
@Autowired
@Qualifier("redisTemplate")
private RedisTemplate template;
spring-data-redis针对jedis客户端中大量api进行了归类封装,将同一类型操作封装为operation接口
ValueOperations:简单K-V操作
ListOperations:list类型数据操作
SetOperations:set类型数据操作
ZSetOperations:zset(sort set)类型数据操作
HashOperations:hash(map)类型数据操作
使用示例
/**
* 设置string类型的键值
* @param key
* @param value
* @param <T>
* @return
*/
public <T> ValueOperations< String, T> setCacheObject( String key, T value){
ValueOperations< String, T> operation = template.opsForValue();
operation.set( key, value);
return operation;
}
/**
* 根据键获取string类型的值
* @param key 键
* @param <T>
* @return
*/
public <T> T getCacheObject( String key){
ValueOperations< String, T> operation = template.opsForValue();
return operation.get( key);
}
/**
* 设置list类型的键值
* @param key
* @param values
* @param <T>
* @return
*/
public <T> ListOperations< String, T> setCacheList( String key, List< T> values){
ListOperations operation = template.opsForList();
if( values != null){
int size = values.size();
for( int i = 0; i <size; i++){
operation.rightPush( key, values.get(i));
}
}
return operation;
}
/**
* 根据键获取list类型的值
* @param key
* @param <T>
* @return
*/
public <T> List<T> getCacheList( String key){
List< T> list = new ArrayList<>();
ListOperations< String, List<T>> operations = template.opsForList();
if( operations != null){
long size = operations.size( key);
for( int i = 0; i< size; i++){
T tmp = (T)operations.leftPop( key);
list.add( tmp);
}
}
return list;
}
4、作为缓存
现在Redis已经能够正常在Spring项目中使用了,但是要作为web系统缓存,还需要再进一步进行配置
一般来说,使用缓存的时候我们会偏好于注解的方式,所以我们得先开启Spring的缓存注解
<!-- 启用缓存注解 -->
<cache:annotation-driven />
此外,还需要定义一个缓存管理器,用来管理缓存
<bean id="cacheManager" class="org.springframework.data.redis.cache.RedisCacheManager"
c:redisOperations-ref="redisTemplate" />
缓存常用的注解有:@Cacheable、@CacheEvict和@CachePut
(1)、@Cacheable( key = “xxx”, value = “xxx” , [ condition = “xxx” ] )
使用了该注解的方法,方法的运行结果将会被装入缓存;而在随后的方法调用中,key一致的情况下优先返回被缓存的值,而不会真正执行这个方法。注意value是必须的,指明了缓存将被存到什么地方;condition指定条件,该条件满足时才会存储方法值;key和condition允许使用SpELl表达式。
(2)、@CachePut( key = “xxx”, value = “xxx”, [ condition = “xxx” ] )
修改缓存,使用了该注解的方法被调用将会修改缓存,而当缓存不存在时将会新增。使用方法与@Cacheable类似
(3)、@CacheEvict( key = “xxx”, value = “xxx”, [ condition = “xxx” ] )
删除缓存,使用了该注解的方法被调用将会删除缓存
使用示例
我们定义CacheDemoService接口并对其进行实现,然后创建一个被@Cacheable注解的方法,方法比较简单,就是返回参数和当前时间的拼接
@Service
public class CacheDemoServiceImpl implements CacheDemoService {
@Cacheable( key = "#name", value = "string")
@Override
public String get( String name) throws Exception {
// 等待10000毫秒,保证当前时间与上次调用时有明显的变化
Thread.sleep( 10000);
return name + new Date().getTime();
}
}
使用方法参数name作为缓存的键,值为方法返回值。我们能够看到,方法里面每次都是获取到的当前的时间都是不一样的
我们写个测试类进行检验
import org.demo.service.CacheDemoService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import static org.junit.Assert.assertEquals;
/**
* Created by zhangcs on 17-4-8.
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration( locations = "classpath:spring.xml")
public class CacheTest {
@Autowired
private CacheDemoService cacheDemoService;
@Test
public void get() throws Exception {
String val1 = cacheDemoService.get( "zhangcs");
String val2 = cacheDemoService.get( "zhangcs");
assertEquals( val1, val2);
}
}
执行测试方法,我们能够看到断言是通过了的,也就是说测试方法里面在参数相同的情况下两次调用方法获取到的结果是一致的。缓存生效,第二次方法调用是直接读取了缓存的值,而没有真正的执行方法
此时的值已经被存储到了Redis服务器中,我们可以使用redis-cli客户端进行查看
我们再来看下修改缓存
@CachePut( key = "#name", value = "string")
@Override
public String put(String name) throws Exception {
return name + new Date().getTime();
}
编写测试方法进行测试,能够看到该方法的断言是不通过的,也就是说修改生效了
@Test
public void put() throws Exception {
String val1 = cacheDemoService.get( "zhangcs");
// 修改缓存
cacheDemoService.put("zhangcs");
String val2 = cacheDemoService.get( "zhangcs");
assertEquals( val1, val2);
}
此时Redis服务器里面存储的值是已经发生了变化的