今天分享 java基于Spring注解的缓存 解决方案;
上篇以代码的方式分享缓存解决方案;今天分享以注解的方式解决缓存问题:
Spring 3.1 起,提供了基于注解的对 Cache 的支持。
一、使用 Spring Cache 的好处:
1、
基于注解,代码清爽简洁;
2、基于注解也可以实现复杂的逻辑;
3、可以对缓存进行回滚;
Spring Cache 并非具体的缓存技术,而是基于各种缓存产品(如 Guava、EhCache、 Redis 等)共性进行的一层封装,结合 SpringBoot 的开箱即用的特性用起来会非常方
便,因为 Spring Cache 通过注解隔离了具体的缓存产品,让用户更加专注于应用层面。 具体的底层缓存技术究竟采用了 Guava、EhCache 还是 Redis,只需要简单的配置就 可以实现方便的切换。
二、设计理念
正如 Spring 框架的其它服务一样,Spring cache 首先是提供了一层抽象,核心抽象 主要体现在两个接口上
org.springframework.cache.Cache:代表缓存本身
org.springframework.cache.CacheManager:代表对缓存的处理和管理
抽象的意义在于屏蔽实现细节的差异和提供扩展性,Cache 的抽象解耦了缓存的使用和 缓存的后端存储,方便后续更换存储。
三、使用 Spring Cache
分三步: 声明缓存 、 开启 Spring 的 cache 功能 、 配置后端的存储
1 声明缓存
@Cacheable("book")
public Book findBook() {...}
用法很简单,在方法上添加@cacheable 等注解,表示缓存该方法的结果。 当方法有被调用时,先检查 cache 中有没有针对该方法相同参数的调用发生过,如果有,
从 cache 中查询并返回结果。如果没有,则执行具体的方法逻辑,并把结果缓存到 cache 中。当然这一系列逻辑对于调用者来说都是透明的。
其它的缓存操作的注解包含如下(详细说明可参见官方文档):
@Cacheable triggers cache population
@CacheEvict triggers cache eviction
@CachePut updates the cache without interfering with the method execution
@Caching regroups multiple cache operations to be applied on a method
@CacheConfig shares some common cache-related settings at class-level
2、 开启 Spring Cache 的支持
<cache:annotation-driven />
或者使用注解@EnableCaching 的方式
@EnableCaching
public class CacheConfig extends CachingConfigurerSupport {
//相关配置暂时省略,后面会讲解
}
3 、配置缓存后端存储
SpringCache 包本身提供了一个 manager 实现,用法如下:
@Bean public CacheManager cacheManager() {
//jdk 里,内存管理器
SimpleCacheManager cacheManager = new SimpleCacheManager();
cacheManager.setCaches(Collections.singletonList(new ConcurrentMapCache("province")));
return cacheManager;
}
更常用的,RedisCacheManager(来自于 Spring Data Redis 项目),用法如下:
@Bean public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
return RedisCacheManager .builder(connectionFactory)
.cacheDefaults( RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(20))) //缓存时间绝对过期时间 20s
.transactionAware() .build();
}
4、 缓存 key 的生成
我们都知道缓存的存储方式一般是 key value 的方式,那么在 Spring cache 里,key 是如何被设置的呢,在这里要引入 KeyGenerator,它负责 key 的生成策略,只需要按
自己的业务,为 params 设计一套生成 key 的规则即可(简单地连接各个参数值):
//key 的生成,springcache 的内容,跟具体实现缓存器无关
@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().getSimpleName());
sb.append(method.getName());
for (Object obj : params) {
sb.append(obj.toString());
}
return sb.toString();
}
};
}
三、 注解风格说明
1 、@CachePut 注解说明
public @interface CachePut {
String[] value();
//缓存的名字,可以把数据写到多个缓存
String key() default "";
// 缓 存 key , 如 果 不 指 定 将 使 用 默 认 的
KeyGenerator 生成,后边介绍
String condition() default ""; //满足缓存条件的数据才会放入缓存,condition在调用方法之前和之后都会判断
String unless() default "";
//用于否决缓存更新的,不像 condition,该表达只在方法执行之后判断,此时可以拿到返回值 result 进行判断了
}
@CachePut 注解使用 应用到写数据的方法上,如新增/修改方法,调用方法时会自动把相应的数据放入缓存。 比如下面调用该方法时,会把 user.id 作为 key,返回值作为 value 放入缓存
2、 @CacheEvict 注解说明
public @interface CacheEvict {
String[] value();
//请参考@CachePut
String key() default "";
//请参考@CachePut
String condition() default "";
//请参考@CachePut
boolean allEntries() default false;
//是否移除所有数据享学课堂
boolean beforeInvocation() default false;//是调用方法之前移除/还是调用之后移除
}
@CacheEvict 注解使用 应用到移除数据的方法上,如删除方法,调用方法时会从缓存中移除相应的数据
3 、@Cacheable 注解说明
public @interface Cacheable {
String[] value();
//请参考@CachePut
String key() default "";
//请参考@CachePut
String condition() default "";//请参考@CachePut
String unless() default "";
//请参考@CachePut
}
@Cacheable 注解使用应用到读取数据的方法上,即可缓存的方法,如查找方法:先从缓存中读取,如果没有 再调用方法获取数据,然后把数据添加到缓存中
四、实战使用:
1、缓存配置:
@Configuration
@EnableCaching
public class CacheConfig extends CachingConfigurerSupport {
//key的生成,springcache的内容,跟具体实现缓存器无关
//自定义本项目内的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().getSimpleName());
sb.append(method.getName());
for (Object obj : params) {
sb.append(obj.toString());
}
return sb.toString();
}
};
}
//不支持过期时间
// @Bean
// public CacheManager cacheManager() {
// //jdk里,内存管理器
// SimpleCacheManager cacheManager = new SimpleCacheManager();
// cacheManager.setCaches(Collections.singletonList(new ConcurrentMapCache("province")));
// return cacheManager;
// }
@Bean
public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
return RedisCacheManager
.builder(connectionFactory)
.cacheDefaults(
RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(20))) //缓存时间绝对过期时间20s
.transactionAware()
.build();
}
/**
* 序列化object对象为json字符串
*/
/* @Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
//使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值
Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
serializer.setObjectMapper(mapper);
template.setValueSerializer(serializer);
//使用StringRedisSerializer来序列化和反序列化redis的key值
template.setKeySerializer(new StringRedisSerializer());
template.afterPropertiesSet();
return template;
}*/
/**
* JdkSerializationRedisSerializer: 序列化java对象(被序列化的对象必须实现Serializable接口),无法转义成对象
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
//使用jdk的序列化
template.setValueSerializer(new JdkSerializationRedisSerializer());
//使用StringRedisSerializer来序列化和反序列化redis的key值
template.setKeySerializer(new StringRedisSerializer());
template.afterPropertiesSet();
return template;
}
}
2、业务代码:
/**
* springcache优雅实现缓存
*/
@Service("TestProvincesService")
@CacheConfig(cacheNames="province") //通用配置
public class ProvincesServiceImpl2 extends ProvincesServiceImpl implements ProvincesService{
// @Cacheable(value = "province",
// key = "#root.targetClass.simpleName+':'+#root.methodName+':'+#provinceid")
@Cacheable// value指定当前接口,要使用哪一个缓存器 --- 如果该缓存器不存在,则创建一个
public Provinces detail(String provinceid) {//一个接口方法,对应一个缓存器
return super.detail(provinceid);
}
//这个AOP,是先删缓存,先改数据库?先删库,在删缓存
@CachePut(key = "#entity.provinceid")
public Provinces update(Provinces entity) {
return super.update(entity);
}
@CacheEvict//先删库,在删缓存
public void delete(String provinceid) {
super.delete(provinceid);
}
//组合配置
@Caching(put = {
@CachePut(key = "#entity.provinceid"),
@CachePut(key = "#entity.provinceid")}
)
public Provinces add(Provinces entity) {
return super.add(entity);
}
这就是注解的缓存解决方案,使用起来很方便,当然凡是都有两面性;相比逻辑代码的实现方案,可读性 差,灵活性小,因此缓存解决方法,一定要根据具体业务场景选择使用。