JSR107缓存规范
JSR107是Java为我们提供的一套缓存规范,它是面向接口编程的,我们想要实现什么功能就去继承相应的接口,并重写对应的方法。不过由于整合的难度较大,现在市面上较少使用,不过后面推出的一些缓存实现机制都是在它基础层面上进行扩展的。所以我们可以了解一下它的基础概念,后面的使用中很少会使用到它了,一般会使用SpringBoot的缓存抽象
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设置
如果我们想要使用JSR107规范,那么需要导入javax.cache
下的cache-api
包
<dependency> |
因为JSR107是一个缓存规范,不是一种实现,全是一些接口,我们需要自己去实现JSR,市面上应用JSR的也不是很多,所以目前我们很少有使用JSR的而多数是使用Spring的缓存抽象。
Spring缓存抽象
Spring从3.1开始定义了org.springframework.cache.Cache和org.springframework.cache.CacheManager接口来统一不同的缓存技术,并且支持JCache(JSR-107)注解简化我们的开发
- Cache接口为缓存的组件规范定义,包含缓存的各种操作集合
- Cache接口下Spring提供了各种xxxCache的实现;如RedisCache,EhCacheCache,ConcurrentMapCache等
- 每次调用需要缓存功能的方法时,Spring会检查指定参数的指定的目标方法是否被调用过;如果有直接从缓存中获取调用后的结果,如果没有就调用方法并缓存结果后返回给用户。下次调用直接从缓存中获取。
- 使用Spring缓存抽象时我们需要关注以下两点
- 确定方法需要被缓存以及他们的缓存策略
- 从缓存中读取的数据是之前缓存存储的数据
几个重要概念&缓存注解
概念和注解
注解/概念 作用 Cache 缓存接口,定义缓存操作。实现有:RedisCache、EhCacheCache、ConcurrentMapCache CacheManager 缓存管理器,管理各种缓存(Cache)组件 @Cacheable 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存 @CacheEvict 清空缓存 @CachePut 保证方法被调用,又希望结果被缓存 @EnableCaching 开启基于注解的缓存 keyGenerator 缓存数据时key的生成策略 serialize 缓存数据时value序列化策略 @Cacheable/@CachePut/@CacheEvict主要参数
参数 | 作用 | 例子 |
---|---|---|
value | 缓存的名称,在Spring配置文件中定义,必须指定至少一个 | @Cacheable(value=”mycache”)或者 @Cacheable(value={“cache1”,”cache2”}) |
key | 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的 所有参数进行组合 |
@Cacheable(value=”testcache”,key=”#userName”) |
condition | 缓存的条件,可以为空,使用 SpEL 编写,返 回 true 或者 false,只有为 true 才进行缓存/清 除缓存 |
@Cacheable(value=”testcache”,condition=”#userName.length()>2”) |
allEntries(@CacheEvict ) | 是否清空所有缓存内容,缺省为 false,如果指 定为 true,则方法调用后将立即清空所有缓存 |
@CachEvict(value=”testcache”,allEntries=true) |
beforeInvocation(@CacheEvict) | 是否在方法执行前就清空,缺省为 false,如果 指定为 true,则在方法还没有执行的时候就清 空缓存,缺省情况下,如果方法执行抛 出异常,则不会清空缓存 |
@CachEvict(value=”testcache”,beforeInvocation=true) |
- 缓存SpEL能使用的参数有哪些
名字 | 位置 | 描述 | 实例 |
---|---|---|---|
methodName | root object | 当前被调用的方法名 | #root.methodName |
method | root object | 当前被调用的方法 | #root.method.name |
target | root object | 当前被调用的目标对象 | #root.target |
targetClass | root object | 当前被调用的目标对象类 | #root.targetClass |
args | root object | 当前被调用的方法的参数列表 | #root.args[0] |
caches | root object | 当前方法调用使用的缓存列表(如@Cacheable(value={“cache1”,”cache2”})) | #root.caches[0].name |
argument name | evaluation context | 方法参数的名字,可以直接 #参数名,也可以使用#p0活着#a0的形式,0代表参数的索引 | #name,#a0,#p0 |
使用Srping Cache
我们使用Spring初始化器,选择IO下的缓存模块就可以将缓存的starter引入
<dependency> |
开启基于注解的缓存
@EnableCaching
//在主程序入口处开启
public class SpringbootCacheApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootCacheApplication.class, args);
}
}标注缓存注解即可
Cacheable注解使用
Cacheable:将方法的运行结果进行缓存,以后再要有相同的数据,直接从缓存中取,不用调用方法
CacheManager管理多个Cache组件的,对缓存的真正CRUD操作再Cache组件中华,每一个缓存组件有自己唯一一个名字;
几个属性:
1) cacheNames/value:指定缓存组件的名字;将方法的返回结果放在放在哪个缓存中,是数组的方式,可以指定多个缓存
2) key:缓存数据使用的key,可以用它来指定。默认是使用方法参数的值,也可以编写SpEL
"emp",key = "#root.methodName+'['+#id+']'") (cacheNames = |
3) keyGenerator:key的生成器,可以自己指定key的生成器组件id key/keyGeneratore二选一,只能有一个使用
如果要使用keyGenerator一共有两步,第一步是往容器中创建一个KeyGenertor类,然后再注解中标记他
//1、往IOC容器中添加 |
4) cacheManager:指定缓存管理器,或者是cacheResolver缓存解析器,二选一
5) condition: 指定符合条件的情况下才缓存
"emp",condition = "#id>1 and #root.methodName eq 'aaa'")//这样是id大于1并且方法名等于aaa才会进行缓存,可以写很多条件 (cacheNames = |
6) unless:否定缓存,当unless为true则不缓存,可以获取到结果进行判断 unless = “#result == null”,与condition用法相反。
7) sync: 是否启用异步模式,是否在缓存结果的时候异步处理,如果是异步unless不生效。
"emp")//需要指定一下. (cacheNames = |
Cacheable运行原理
1、从我们缓存的自动配置类入手,因为SpringBoot一切的都是从自动配置开始的。
.class, CacheManagerEntityManagerFactoryDependsOnPostProcessor.class }) ({ CacheConfigurationImportSelector |
2、CacheConfigurationImportSelector
类会往容器中导入一些缓存用到的组件
static class CacheConfigurationImportSelector implements ImportSelector { |
这里导入了很多缓存的配置信息
//org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration |
3、我们在配置文件中把debug配置为true,会发现SimpleCacheConfiguration
生效了。给容器中注册了一个CacheManager:ConcurrentMapCacheManager
false) (proxyBeanMethods = |
4、 ConcurrentMapCacheManager的作用是,可以获取和创建ConcurrentMapCache类型的缓存组件
public class ConcurrentMapCacheManager implements CacheManager, BeanClassLoaderAware { |
5、ConcurrentMapCache的作用就是把数据存放在ConcurrentMap中,
public class ConcurrentMapCache extends AbstractValueAdaptingCache { |
6、缓存运行流程:
- 在调用方法之前会进入
ConcurrentMapCacheManager
的getcache方法中,先去查询Cache(缓存组件),按照cacheNames指定的名字获取,第一次获取的时候如果没有cache组件会自动创建一个。
public Cache getCache(String name) {//会去找cacheable的name
Cache cache = this.cacheMap.get(name);//第一次是没有的
if (cache == null && this.dynamic) {
synchronized (this.cacheMap) {//这里创建缓存的时候是线程安全的
cache = this.cacheMap.get(name);
if (cache == null) {//第一次获取缓存会创建一个
cache = createConcurrentMapCache(name);
this.cacheMap.put(name, cache);
}
}
}
return cache;
} - 去cache中查找缓存的内存,使用一个key:默认就是方法的参数,默认使用keyGenerator生成的,默认使用SimpleKeyGenerator生成key
// 去Cache中查找
protected Object lookup(Object key) {//这里的key就是默认为方法的参数
return this.store.get(key);
}
//那么这个key是如何生成的呢,SimpleKeyGenerator的生成策略
public class SimpleKeyGenerator implements KeyGenerator {
public Object generate(Object target, Method method, Object... params) {
return generateKey(params);
}
/**
* Generate a key based on the specified parameters.
*/
public static Object generateKey(Object... params) {
if (params.length == 0) {//如果参数是0,使用空值
return SimpleKey.EMPTY;
}
if (params.length == 1) {
Object param = params[0];
if (param != null && !param.getClass().isArray()) {
return param;
}
}
return new SimpleKey(params);//根据参数来进行设置
}
} - 没有查找到缓存,就调用目标方法
- 将目标方法返回的结果放进缓存中,调用
ConcurrentMapCacheManager
的put方法放到内存中。 - @Cacheable标注的方法执行之前先来检查缓存中是不是有这个数据,默认按照参数的值作为key去查询缓存,如果没有就运行方法,并将结果放入缓存。
- 第二次执行的之后就直接从缓存中进行查找。
CachePut注解使用
CachePut:既调用方法,又更新缓存数据。使用场景就是修改了数据库的某个数据,同时要更新缓存
运行时机:
- 调用目标方法
- 将目标方法缓存起来
- 不同于Cacheable先走注解,再走方法,CachePut是先走方法,返回值直接缓存
注意事项:
- 一般CachePut是与Cacheable配合使用的,那么就是说我Cacheable缓存的结果想要通过CachePut修改,key值必须相同,且cacheNames也要相同才能找到进行修改。
"emp",key = "#employee.id")//这里想更新原有的Cacheable放进去的缓存,需要设置相同的key和cachename (cacheNames = |
CacheEvict注解使用
用于删除缓存中的数据。默认在方法之后执行。
- CacheEvict特有属性:
- allEntries = true: 指定清除缓存中所有的数据。
- beforeInvocation = false 是否在方法之前清除
"emp",key = "#id")//不写key默认是用方法参数 (cacheNames =
public void deleteEmp(Integer id)
{
employeeMapper.deleteEmp(id);
}
Caching注解使用
Caching注解包含了三个注解,主要是为了应对比较复杂的情况
public Caching { |
示例如下:
//这样使用组合注解的方式进行注解开发 |
CacheConfig注解使用
如果我们的方法都需要缓存注解,那么在类上加一个CacheConfig即可,可以指定一些公共的属性。
"emp")//在类上面进行标记以后,下面方法的就不需要标记了。 (cacheNames = |
SpringBoot整合Redis
在实际开发中一般使用缓存中间件的方式进行开发
关于redis的使用可以查看官网:http://www.redis.cn/commands.html
如果想要使用redis,需要引入redis的starter
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>这样就将redis的starter引入进来了。
在配置文件中对redis进行配置
spring:
redis:
host: 192.168.1.8这是Redis中常见的配置。
#redis
# Redis服务器地址
10.11.12.237 =
# Redis服务器连接端口
6379 =
# Redis数据库索引(默认为0)
0 =
# Redis服务器连接密码(默认为空)
=
# 连接超时时间(毫秒)
10000 =
# 以下连接池已在SpringBoot2.0不推荐使用
#spring.redis.pool.max-active=8
#spring.redis.pool.max-wait=-1
#spring.redis.pool.max-idle=8
#spring.redis.pool.min-idle=0
# Jedis
#spring.redis.jredis.max-active=8
#spring.redis.jredis.max-wait=10000
#spring.redis.jredis.max-idle=8
#spring.redis.jredis.min-idle=0
# Lettuce
# 连接池最大连接数(使用负值表示没有限制)
8 =
# 连接池最大阻塞等待时间(使用负值表示没有限制)
10000 =
# 连接池中的最大空闲连接
8 =
# 连接池中的最小空闲连接
0 =
# 关闭超时时间
100 =测试redis是否配置正确
StringRedisTemplate stringRedisTemplate;//操作字符串
RedisTemplate redisTemplate;//操作对象
/*
Redis常见的五大数据结构
String(字符串),List(列表),Set(集合),Hash(散列),Zset(有序集合)
stringRedisTemplate.opsForValue();[操作字符串]
*/
public void redistest()
{
stringRedisTemplate.opsForValue().set("msg","你好");
}使用客户端进行查看,就可以查看到相应的信息,这里的使用都对应这redis中的操作可以去官网进行查看
对象的序列化问题
因为在序列化的时候RedisTemplate默认使用的序列化器是JDK的序列化器,这样会导致乱码。
1、第一种方法就是就是序列化成json然后用字符串的方式进行保存
2、就是实现自己的序列化器替换掉Redis客户端使用的默认序列化器。这种是直接使用RedisTemplate时生效。
public class MyRedisConfig {
public RedisTemplate<Object, Employee> EmpredisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
RedisTemplate<Object, Employee> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
template.setDefaultSerializer(new Jackson2JsonRedisSerializer<Employee>(Employee.class));
return template;
}
}//实现自己额序列化器
RedisTemplate<Object,Employee> EmpredisTemplate ;
public void redistest()
{
Employee emp = employeeMapper.getEmpById(1);
EmpredisTemplate.opsForValue().set("emp",emp);//测试类
}这样就可以实现json的写入了。
3、自定义RedisCacheManager,使用GenericJackson2JsonRedisSerializer类对value进行序列化,这种序列化起作用的入口是直接使用CacheManager获取Cache,利用Cache操作Redis时才生效。Cache cache = redisCacheManager.getCache("");
cache.put();
public class MyRedisConfig {
RedisCacheManager cacheManager(RedisConnectionFactory factory){
//创建默认RedisCacheWriter
RedisCacheWriter cacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(factory);
//创建默认RedisCacheConfiguration并使用GenericJackson2JsonRedisSerializer构造的 SerializationPair对value进行转换
//创建GenericJackson2JsonRedisSerializer的json序列化器
GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
//使用json序列化器构造出对转换Object类型的SerializationPair序列化对
RedisSerializationContext.SerializationPair<Object> serializationPair = RedisSerializationContext.SerializationPair.fromSerializer(jsonRedisSerializer);
//将可以把Object转换为json的SerializationPair传入RedisCacheConfiguration
//使得RedisCacheConfiguration在转换value时使用定制序列化器
RedisCacheConfiguration cacheConfiguration=RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(serializationPair);
RedisCacheManager cacheManager = new RedisCacheManager(cacheWriter,cacheConfiguration);
return cacheManager;
}
}
更多的关于redis的使用在redis里面进行讲解。
SpringBoot中Redis序列化原理
配置类RedisCacheConfiguration向容器中导入了其定制的RedisCacheManager,在默认的RedisCacheManager的配置中,是使用jdk序列化value值
false) (proxyBeanMethods = |
RedisCacheManager的直接构造类,该类保存了配置类RedisCacheConfiguration,该配置在会传递给RedisCacheManager,RedisCacheManager是通过RedisCacheManagerBuilder创建的
public static class RedisCacheManagerBuilder { |
RedisCacheConfiguration保存了许多缓存规则,这些规则都保存在RedisCacheManagerBuilder的RedisCacheConfiguration defaultCacheConfiguration属性中,并且当RedisCacheManagerBuilder创建RedisCacheManager传递过去
public class RedisCacheConfiguration { |
RedisCacheManager在创建RedisCache时将RedisCacheConfiguration传递过去,并在创建RedisCache时通过createRedisCache()起作用
public class RedisCacheManager extends AbstractTransactionSupportingCacheManager { |
RedisCache,Redis缓存,具体负责将缓存数据序列化的地方,将RedisCacheConfiguration的序列化对SerializationPair提取出来并使用其定义的序列化方式分别对k和v进行序列化操作
public class RedisCache extends AbstractValueAdaptingCache { |
分析到这也就不难理解,要使用json保存序列化数据时,需要自定义RedisCacheManager,在RedisCacheConfiguration中定义序列化转化规则,并向RedisCacheManager传入我们自己定制的RedisCacheConfiguration了,我定制的序列化规则会跟随RedisCacheConfiguration一直传递到RedisCache,并在序列化时发挥作用。