一、前置知识:JCache
在Spring Boot中使用缓存之前,我们有必要了解一下JCache。
JCache定义了五个核心接口,分别是CachingProvider,CacheManager,Cache,Entry和Expiry。
- 一个CachingProvider可以创建和管理多个**CacheManager**,并且一个CacheManager只能被一个CachingProvider所拥有,而一个应用可以在运行期间访问多个CachingProvider。
- 一个CacheManager可以创建和管理多个唯一命名的**Cache**,且一个Cache只能被一个CacheManager所拥有,这些Cache存在于CacheManager的上下文中。
- Cache是一个类似Map的数据结构
- Entry是一个存储在Cache中的key-value对
- Expiry是指存储在Cache中的Entry的有效期,一旦超过这个时间,Entry将处于过期状态,即不可访问、更新和删除。缓存有效期可以通过ExpiryPolicy设置。
二、 Spring Cache原理
Spring 3.1开始,引入了Spring Cache,即Spring 缓存。
-
通过定义org.springframework.cache.Cache和org.springframework.cache.CacheManager接口来统一不同的缓存技术,并支持使用JCache注解简化开发过程。
-
Cache接口包含缓存的各种操作集合。
-
Spring中为Cache接口提供了各种xxxCache的实现:RedisCache,EhCacheCache,ConcurrentMapCache等。
(ConcurrentMapCache无法实现持久化缓存,是Spring默认的Cache实现)
CacheManager
CacheManager 基于 name 管理一组 Cache。
public interface CacheManager {
@Nullable
//获取指定name的Cache,可能延迟创建
Cache getCache(String name);
//获取当前CacheManager下的Cache name集合
Collection<String> getCacheNames();
}
三、Spring Cache实践
在Spring Cache时,还可以通过 JCache注解来实现缓存的功能。
@Cacheable、@CacheEvict和@CachePut三个注解都是对方法进行配置
Cache中可用的SpEL表达式
@EnableCaching
这个注解表示开启基于注解的缓存,一般放在主程序类前面
@Cacheable
这个注解放在方法前,可以将方法的运行结果进行缓存,之后就不用调用方法了,直接从缓存中取值即可。
- @Cacheable注解中常用的参数有cacheNames/value(指定缓存组件的名字,可以指定多个)
- key(缓存数据时使用的key,默认使用方法参数的值,也可以自定义)
- keyGenerator(key的生成器,可以自定义,key与keyGenerator二选一)
- cacheManager(指定缓存管理器,或者使用cacheResolver指定获取解析器)
- condition(符合条件才缓存)、
- unless(符合条件则不缓存,可以获取方法运行结果进行判断)
- sync(是否使用异步模式,不可与unless一起使用)。
@Cacheable(cacheNames = {"emp"}, key = "#id", conditon = "mid>0", unless = "#result == null")
public Employee getEmp(Integer id) {
Employee emp = employeeMapper.getEmpById(id);
return emp;
}
@Cacheable的运行原理
- 方法运行前,程序会使用cacheManager根据cacheNames获取Cache,如果没有对应名称的Cache,则自动创建一个Cache。
- 使用key去Cache中查找对应的缓存内容。
key默认使用SimpleKeyGenerator生成,其生成策略如下:
- 如果没有参数,key=new SimpleKey()
- 如果只有一个参数,key=参数值
- 如果有多个参数,key=new SimpleKey(params)
- 没有查到缓存值,则调用目标方法
- 以步骤二中返回的key,目标方法返回的结果为value,存入缓存
@CachePut
@CachePut注解先调用目标方法,然后再缓存目标方法的结果。
@CachePut(value = "emp", key = "#result.id")
public Employee updateEmp(Employee employee) {
employeeMapper.updateEmp(employee);
return employee;
}
@CacheEvict
@CacheEvict用于清除缓存,可以通过key指定需要清除的缓存,allEntries置为true表示删除所有缓存。
@CacheEvict(value = "emp", key = "#id", allEntries = true)
public void deleteEmp(Integer id) {
employeeMapper.deleteEmpById(id);
}
默认删除缓存行为在方法执行之后(beforeInvocation=false),如果方法运行异常,则该缓存不会被删除。但可以通过设置beforeInvocation = true,将删除缓存行为在方法执行之前。
@Caching&@CacheConfig
@Caching注解中包含了@Cacheable、@CachePut和@CacheEvict注解,可以同时指定多个缓存规则。示例如下所示:
@Caching(
cacheable = {
@Cacheable(value = "emp", key = "#lastName")
},
put = {
@CachePut(value = "emp", key = "#result.id")
@CachePut(value = "emp", key = "#result.email")
}
)
@CacheConfig注解放在类上,抽取缓存的公共配置,如cacheNames、cacheManager等,这样就不用在每个缓存注解中重复配置了。
@Cacheable和@CachePut的区别
相同:都是Spring的缓存注解
不同点:
-
@Cacheable:只会执行一次,标记在一个方法上时表示该方法是支持缓存,方法被调用后将其返回值缓存起来,下次再执行该方法时可以直接从缓存中获取结果。
-
@CachePut标注的方法在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中。
四、自定义配置
使用Redis实现Spring cache时。
Redis自定义key规则
通过自定义KeyGenerator就不需要在每一个缓存方法上自定义key的规则了。
以下配置的规则是使用类名+方法名+参数值来作为key的规则。
@Bean
public KeyGenerator keyGenerator() {
return new KeyGenerator() {
@Override
public Object generate(Object target, Method method, Object... params) {
StringBuilder sb = new StringBuilder();
sb.append(target.getClass().getName());
sb.append(method.getName());
for (Object obj : params) {
sb.append(obj.toString());
}
return sb.toString();
}
};
}
自定义RedisTemplate
被序列化的对象必须实现Serializable接口
- RedisTemplate用的key和value的序列化方式是默认的 JdkSerializationRedisSerializer,采用这种序列化方式,redis缓存中的key和value是乱码的。
- 通过使用StringRedisSerializer(字符串形式)以及Jackson2JsonRedisSerializer来设置序列化方式,就可以避免乱码。
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(connectionFactory);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
// 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会跑出异常```
om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,
ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
jackson2JsonRedisSerializer.setObjectMapper(om);
//设置key序列化方式string
redisTemplate.setKeySerializer(new StringRedisSerializer());
//设置value的序列化方式json
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
//设置hash key序列化方式string
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
//设置hash value的序列化方式json
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
//初始化RedisTemplate的一些参数设置,如果不执行此方法,可能会报一些错误
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}