先来谈谈JSR107(Java Cache API)
Java Caching定义了5个核心接口,分别是CachingProvider, CacheManager, Cache, Entry 和 Expiry。
•CachingProvider定义了创建、配置、获取、管理和控制多个CacheManager。一个应用可以在运行期访问多个CachingProvider。
•CacheManager定义了创建、配置、获取、管理和控制多个唯一命名的Cache,这些Cache存在于CacheManager的上下文中。一个CacheManager仅被一个CachingProvider所拥有。
•Cache是一个类似Map的数据结构并临时存储以Key为索引的值。一个Cache仅被一个CacheManager所拥有。
•Entry是一个存储在Cache中的key-value对。
•Expiry 每一个存储在Cache中的条目有一个定义的有效期。一旦超过这个时间,条目为过期的状态。一旦过期,条目将不可访问、更新和删除。缓存有效期可以通过ExpiryPolicy设置。
Spring 缓存抽象
Spring从3.1开始定义了org.springframework.cache.Cache
和org.springframework.cache.CacheManager接口来统一不同的缓存技术;
并支持使用JCache(JSR-107)注解简化我们开发;
•Cache接口为缓存的组件规范定义,包含缓存的各种操作集合;
•Cache接口下Spring提供了各种xxxCache的实现;如RedisCache,EhCacheCache , ConcurrentMapCache等;
•每次调用需要缓存功能的方法时,Spring会检查检查指定参数的指定的目标方法是否已经被调用过;如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果后返回给用户。下次调用直接从缓存中获取。
•使用Spring缓存抽象时我们需要关注以下两点;
1、确定方法需要被缓存以及他们的缓存策略
2、从缓存中读取之前缓存存储的数据
如何使用
这里我们使用ConcurrentMapCacheManager和EhCacheCacheManager为例,EhCacheCacheManager需要引入jar包
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>2.10.4</version>
</dependency>
<?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:cache="http://www.springframework.org/schema/cache"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache.xsd">
<!--
注意需要引入cache空间
xmlns:cache="http://www.springframework.org/schema/cache"
xsi:schemaLocation="http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd"
-->
<!-- 创建spring的缓存管理器,这里使用ConcurrentMapCacheManager -->
<bean id="concurrentMapCacheManager" class="org.springframework.cache.concurrent.ConcurrentMapCacheManager">
<property name="cacheNames">
<set>
<value>default</value> <!-- 创建缓存,缓存名称为default -->
<value>user</value> <!-- 创建缓存,缓存名称为user -->
</set>
</property>
</bean>
<!-- 创建一个spring的缓存管理器,这里使用ehCacheCacheManager -->
<bean id="ehCacheCacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager">
<!-- 注入net.sf.ehcache.CacheManager交给spring管理 -->
<property name="cacheManager">
<!-- 通过工厂创建一个net.sf.ehcache.CacheManager -->
<bean class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
<property name="configLocation" value="classpath:ehcache.xml" /> <!-- ehcache缓存配置文件 -->
</bean>
</property>
</bean>
<!-- 启用缓存注解 -->
<!--
cache-manager:缓存管理器bean的id,缺省值为 cacheManager,有什么用?
如果设置了cache-manager,当使用缓存注解@Cacheable(cacheManager="concurrentMapCacheManager")时,cacheManager可以省略不写
-->
<cache:annotation-driven cache-manager="concurrentMapCacheManager" />
</beans>
使用注解操作缓存
/**
* @Cacheable 注解用来将方法返回的结果放入缓存中(先检查是否有缓存,没有缓存才执行方法),有以下参数
* cacheManager:使用哪一个缓存管理器,可以为空,使用缺省值;缺省值的使用见本文章xml配置里面的说明
* cacheNames:存放到哪个缓存中,数组类型,可以写多个cacheNames={"default","dept"},也可以使用values="dept"
* keyGenerator:key的生成器,可以不写,使用默认的生成器
* key:指定key的值,使用spel表达式,key="#id"(使用参数的id作为key),key="#user.id"(使用参数user对象中的id作为key)如果为空,则按生成器的策略生成key,默认生成器策略:如果只有一个参数,则使用这个参数值作为key,如果有多个,则使用所有的参数加起来的hash值作为key
* condition:指定符合条件的情况下才缓存,condition = "#id>0"
* unless:不缓存;当unless指定的条件为true,方法的返回值就不会被缓存;可以获取到结果进行判断unless = "#result == null or #result eq ''",返回结果为null就不缓存
* sync:是否使用异步模式,默认false方法执行完后同步进行缓存,如果为true,则异步进行缓存,异步模式不支持unless
*/
@Cacheable(cacheNames="default",key="#id")
public String getDefault(int id) {
return this.getDept(id);
}
@Cacheable(cacheManager="ehCacheCacheManager",cacheNames="dept")
public String getDept(int id) {
return "getDept" + System.currentTimeMillis();
}
/**
* @CachePut 注解不管缓存是否存在都会进入方法,然后把结果缓存,主要用于更新数据
* key="#employee.id" 也可以使用 key="#result.id"
*
*/
@CachePut(cacheNames="default",key="#employee.id")
public Employee updateEmp(Employee employee){
employeeMapper.updateEmp(employee);
return employee;
}
/**
* @CacheEvict:缓存清除
* key:指定要清除的数据
* allEntries = true:指定清除这个缓存中所有的数据
* beforeInvocation:缓存的清除是否在方法之前执行 ,默认false,如果出现异常缓存就不会清除,true 方法执行之前清除,无论方法是否出现异常,缓存都清除
* allEntries 是否删除该缓存下的所有数据(cacheNmae=emp的),默认false
*
*/
@CacheEvict(value="emp",beforeInvocation = true)
public void deleteEmp(Integer id){
int i = 10/0;
}
// @Caching 定义复杂的缓存规则
@Caching(
cacheable = {
@Cacheable(value="emp",key = "#lastName")
},
put = {
@CachePut(value="emp",key = "#result.id"),
@CachePut(value="emp",key = "#result.email")
},
evict = {
@CacheEvict(value="emp",key="#lastName")
}
)
public Employee getEmpByLastName(String lastName){
return employeeMapper.getEmpByLastName(lastName);
}
//@CacheConfig(cacheManager="ehCacheCacheManager",cacheNames="default")
//@CacheConfig 在类上使用,抽取缓存的公共配置
//public class UserCacheService {}
使用自定义的KeyGenerator
@Component
public class MyKeyGenerator implements org.springframework.cache.interceptor.KeyGenerator { //实现KeyGenerator
@Override
public Object generate(Object target, Method method, Object... params) {
//输出:com.fawa.cache.CacheServicegetDefault[1]
//target: com.fawa.cache.CacheService 引用注解的类
//method:getDefaul 引用注解的类的方法
//params:[1] 引用注解的类的方法的参数
System.out.println(target.getClass().getName() + method.getName() + Arrays.asList(params));
return target.getClass().getName() + method.getName() + Arrays.asList(params);
}
}
//keyGenerator="myKeyGenerator" myKeyGenerator为bean的id,myKeyGenerator必须交给spring管理
@Cacheable(cacheNames="default",keyGenerator="myKeyGenerator")
public String getDefault(int id) {
return "getDefault" + System.currentTimeMillis();
}
Cache Key SpEL表达式可使用的变量
除了可以使用参数中值作为变量外,还可以使用以下值
使用编码的方式操作缓存
@Controller
@RequestMapping("/test/")
public class TestController {
//注入缓存接口,当有多个实现类的时候,需要指定cacheManager的实现类
@Autowired
@Qualifier("ehCacheCacheManager") //该bean已在xml中定义
private CacheManager cacheManager; //org.springframework.cache.CacheManager
//或者直接注入缓存实现类
/*@Autowired
private EhCacheCacheManager cacheManager;*/
@RequestMapping("test1")
@ResponseBody
public String test1(){
Collection<String> cacheNames = cacheManager.getCacheNames(); //获取所有缓存组件的名字
Cache cache = cacheManager.getCache("user"); //根据cacheName获取缓存组件
cache.put(1, System.currentTimeMillis()); //添加缓存
Long value = cache.get(1,Long.class); //获取缓存
cache.evict(1); //根据key清除缓存
cache.clear(); //清空缓存
return "success";
}
}
使用RedisCacheManager
<!--2.x 以上版本需要jdk1.8 -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>1.8.13.RELEASE</version>
</dependency>
<!-- 连接池配置 -->
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxTotal" value="100" />
<property name="maxIdle" value="50" />
<property name="minIdle" value="10" />
<!-- 其他属性到源码中查看,这里我们使用默认值 -->
</bean>
<!-- 使用工厂创建连接 -->
<bean id="jedisConnFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<property name="poolConfig" ref="jedisPoolConfig" />
<property name="hostName" value="192.168.1.3" />
<property name="port" value="6379" />
<!-- <property name="password" value="123456" /> -->
<!-- 其他属性到源码中查看,这里我们使用默认值 -->
</bean>
<!-- 使用RedisTemplate,key value 的存储采用序列化,可以配置采用哪种序列化接口,这里我们使用默认配置-->
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="jedisConnFactory" />
<!-- 其他属性到源码中查看 -->
</bean>
<!-- 使用StringRedisTemplate,key value 的存储全部使用string类型-->
<bean id="stringRedisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">
<property name="connectionFactory" ref="jedisConnFactory" />
</bean>
<!-- 创建缓存管理器,这里使用RedisCacheManager -->
<bean id="redisCacheManager" class="org.springframework.data.redis.cache.RedisCacheManager">
<constructor-arg name="redisOperations" ref="stringRedisTemplate" /> <!-- 注入redisOperation接口的实现类,redisTemplate或stringRedisTemplate -->
</bean>
//如果使用的是stringRedisTemplate,key value 都必须是stirng类型
@Cacheable(cacheManager="redisCacheManager",cacheNames="role")
public String getRole(String id) {
return "getRole" + System.currentTimeMillis();
}