16课:关于Springboot和@Cacheable注解拉去缓存,@CacheEvict清空缓存的原理
简介
关于项目中使用缓存的方式各不相同,今天来完成通过@Cacheable 注解完成方法调用拉去缓存的操作内容;首先@Cacheable 注解生成的数据类型就是key-value类型的;只是会多生成一个
代码展示
1.pox.xml
引入redis的start maven配置
<!-- redis 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-redis</artifactId>
<version>1.4.3.RELEASE</version>
</dependency>
2.application.properties文件
配置springboot默认的redis 的配置项内容;测试使用的额的是单机环境
spring.redis.database=0
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=khanyu
spring.redis.pool.max-active=8
spring.redis.pool.max-wait=-1
spring.redis.pool.max-idle=8
spring.redis.pool.min-idle=0
spring.redis.timeout=5000
3.RedisCacheableConfig缓存配置类内容;
首先redis默认的key和value序列化方式不太方便在工具类中展示所以使用自定义的序列化方式处理key和value值
FastjsonRedisSerializer 是自定义的序列化方式类,通过fastJson处理
RedisConnectionFactory 是springboot默认通过加载配置给初始化的
@Bean
public RedisTemplate<String, String> redisTemplate(@Autowired RedisConnectionFactory factory) {
StringRedisTemplate template = new StringRedisTemplate(factory);
template.setKeySerializer(new StringRedisSerializer());//设置redis中缓存的key 的序列化/反序列化方式
template.setValueSerializer(new FastjsonRedisSerializer());//设置redis 中value 的序列化/反序列化方式
template.afterPropertiesSet();
return template;
}
这里配置的是RedisCacheManager 实现@Cacheable注解内容的cacheManager 属性
指定缓存时指定到redis中 还是我们本地的内存中 这个里面配置的是redis中的配置
里面的Map中配置的是各个key 值缓存的时间,然后默认的key配置时间
@Bean("redisCacheManager")
@Primary
public RedisCacheManager initRedisCacheManager(@Autowired RedisTemplate redisTemplate){
RedisCacheManager redisCacheManager = new RedisCacheManager(redisTemplate);
Map<String, Long> expires = new HashMap<>();
expires.put("user",1000L);//设置redis中指定key 的缓存时间
expires.put("khy",1000L);//设置redis中指定key 的缓存时间
//.... 每个key 都可以在这里设置
redisCacheManager.setExpires(expires);
redisCacheManager.setDefaultExpiration(1500);//设置默认的key缓存失效时间
return redisCacheManager;
}
配置的是默认的本地的缓存内容配置方式
/**
* 设置缓存到本地配置的FactoryBean
* @author khy
* @createTime 2020年11月18日下午3:43:47
* @return
*/
@Bean
public ConcurrentMapCacheFactoryBean initConcurrentMapCacheFactoryBean(){
ConcurrentMapCacheFactoryBean cacheFactoryBean = new ConcurrentMapCacheFactoryBean();
cacheFactoryBean.setName("khy");
return cacheFactoryBean;
}
/**
* @Cacheable 注解中cacheManager 指定对应的manager类型 是本地还是依赖Redis
* @author khy
* @createTime 2020年11月18日下午3:46:21
* @param concurrentMapCache
* @return
*/
@Bean("simpleCacheManager")
public SimpleCacheManager initLocalCacheManager(@Autowired ConcurrentMapCache concurrentMapCache){
SimpleCacheManager simpleCacheManager = new SimpleCacheManager();
List<Cache> caches = new ArrayList<Cache>(){{
add(concurrentMapCache);
}};
simpleCacheManager.setCaches(caches);
return simpleCacheManager;
}
配置的是自定义的缓存key 的生成方式;
/**
* 这个是设置前缀的方式
* 下面的代码会用到的;
* @author khy
* @createTime 2020年10月22日上午10:20:02
* @return
*/
@Bean("prefixKeyGenerator")
public PrefixKeyGenerator initPrefixKeyGenerator(){
PrefixKeyGenerator prefixKeyGenerator = new PrefixKeyGenerator("prefix");
return prefixKeyGenerator;
}
自定义缓存key的生成器
public class PrefixKeyGenerator extends SimpleKeyGenerator {
private static final String SLOT_CHANGER = "{Qi}";
private static final String COLON = ":";
private String module;
public PrefixKeyGenerator(String module) {
this.module = module;
}
/**
* 自定义的前缀设置(通过将传递的对象转化成key然后进行拼接使用)
*
* ToStringStyle.SHORT_PREFIX_STYLE
* 这个是无类前缀的toString样式,使用User实例用ToStringBuilder类输出的结果是
*/
public Object generate(Object target, Method method, Object... params) {
StringBuilder key = new StringBuilder();
Object result = super.generate(target, method, params);
if (result instanceof String && JedisClusterCRC16.getSlot((String) result) == 0) {
result = result + "{Qi}";
}
// 拼接的是 前缀:方法class类.方法名:result
//如果不同包下面出现相同的class相同名称的则需要获取target.getClass().getName(); 全路径的class名称
key.append(this.module).append(":").append(target.getClass().getSimpleName()).append(".")
.append(method.getName()).append(":").append(result);
return key.toString();
}
}
4.CacheableController
通过请求本地的代码实现将缓存设置到redis中
http://localhost:8080/cacheable/getSimpleString1?userName=candy
http://localhost:8080/cacheable/getSimpleString1?userName=candy2
图1.在工具中查看到缓存值是根据我们的请求参数是的值来进行缓存的
再次去请求下面的链接因为缓存效果是一样的所以不会再进入方法中
http://localhost:8080/cacheable/getSimpleString2?userName=candy
http://localhost:8080/cacheable/getSimpleString2?userName=candy2
图2.调用下面的方法会在两个zset缓存
http://localhost:8080/cacheable/getSimpleString3?userName=candy2
/**
* 最简单的缓存设置方式
* 请求指定参数内容;然后缓存响应的结果到redis缓存中去
* 当前缓存会设置两种缓存格式
* 一种是String 类型请求参数中的 userName的取值 value 是响应结果
* 一种是zset格式 key =khy~keys row是userName 的值
*
* 同时因为指定的value都是 khy 在 RedisCacheableConfig 中的 RedisCacheManager 配置的 khy 的缓存存活时间是1000s
* 所以两种缓存的有效时间都是1000;
*
* 第一次请求 http://localhost:8080/cacheable/getSimpleString1?userName=candy
* 则方法中的 内容会被打印 但是在此请求的时间 因为userName=candy参数不变则直接从缓存中获取,不再进入方法
* 虽然多次请求但是对应的String 类型的缓存的candy的有效时间是不会被更新的
*
* 后面请求http://localhost:8080/cacheable/getSimpleString1?userName=candy1
* 则 新插入String类型的缓存 同事更新 khy~keys 中数据新增一条;
* @author khy
* @createTime 2020年10月22日上午10:28:23
* @param userName
* @return
*/
@RequestMapping("/getSimpleString1")
@Cacheable(cacheManager="redisCacheManager" ,value="khy")
public String getSimpleString1(String userName){
System.out.println("getSimpleString 进入方法执行了"+userName);
return "返回的userName="+userName;
}
/**
* 和getSimpleString1 返回的结果是一致的;
* @author khy
* @createTime 2020年10月22日上午10:52:31
* @param userName
* @return
*/
@RequestMapping("/getSimpleString2")
@Cacheable(cacheManager="redisCacheManager" ,value="khy" ,key="#userName")
public String getSimpleString2(String userName){
System.out.println("getSimpleString2 进入方法执行了"+userName);
return "返回的userName="+userName;
}
/**
* String 类型的缓存和getSimpleString1 返回的结果是一致的;
* 但是因为value 指定了两种 ;所以生成2中的 zset
* 分别 是 khy~keys 和 111~keys
* 因为 RedisCacheableConfig 中 RedisCacheManager 配置的默认时间是1500秒
* @author khy
* @createTime 2020年10月22日上午11:00:53
* @param userName
* @return
*/
@RequestMapping("/getSimpleString3")
@Cacheable(cacheManager="redisCacheManager" ,value={"khy","111"})
public String getSimpleString3(String userName){
System.out.println("getSimpleString3 进入方法执行了"+userName);
return "返回的userName="+userName;
}
2.多请求参数的缓存的key 通过表达式获取请求参数中的值作为key 来实现缓存的
http://localhost:8080/cacheable/getSimpleString5?userName=candy&userId=111
http://localhost:8080/cacheable/getSimpleString5?userName=candy&userId=1112
如果直接用对象去接受参数,则通过表达式获取对象中的某个属性作为key
http://localhost:8080/cacheable/getSimpleEntity4?userName=candy&password=1112
/**
* 虽然传递多个请求参数 但是key 中指定的#userName 作为key值
* 则主要是根据userName的值 虽然后面的userId参数修改但是也不会进入当前方法
* @author khy
* @createTime 2020年10月22日上午11:39:03
* @param userId
* @param userName
* @return
*/
@RequestMapping("/getSimpleString4")
@Cacheable(cacheManager="redisCacheManager" ,value="khy",key="#userName")
public String getSimpleString4(Integer userId,String userName){
System.out.println("getSimpleString4 进入方法执行了"+userName+userId);
return "返回的userName="+userName;
}
/**
* 和getSimpleString4 中的却别在于 将参数叠加在一起处理了;
* 形成的key是根据userId_userName 两者决定的
* @author khy
* @createTime 2020年10月22日下午1:36:35
* @param userId
* @param userName
* @return
*/
@RequestMapping("/getSimpleString5")
@Cacheable(cacheManager="redisCacheManager" ,value="khy",key="#userId+'_'+#userName")
public String getSimpleString5(Integer userId,String userName){
System.out.println("getSimpleString5 进入方法执行了"+userName+userId);
return "返回的userName="+userName;
}
/**
* 这个里面是将请求参数中的 userName 和password 组合起来作为redis中的key
* @author khy
* @createTime 2020年11月3日上午9:18:14
* @param userEntity
* @return
*/
@RequestMapping("/getSimpleEntity4")
@Cacheable(cacheManager="redisCacheManager" ,value="user",key="#userEntity.userName+'_'+#userEntity.password")
public UserEntity getSimpleEntity4(UserEntity userEntity){
UserEntity cacheEntity = new UserEntity();
cacheEntity.setId(1);
cacheEntity.setAge(10);
cacheEntity.setUserName("小康康");
cacheEntity.setPassword("password123");
cacheEntity.setCreateTime(new Date());
System.out.println("getSimpleEntity4 进入方法内容");
return cacheEntity;
}
5.PrefixKeyController 带有自定义key的
请求通过请求下面的链接在redis中设置的key内容
http://localhost:8080/prefixKey/getSimpleString1?userName=candy
http://localhost:8080/prefixKey/getSimpleString1?userName=candy1
生成的key 的类型下图 这个是在自定义的前缀生成器中
/**
* 通过prefixKeyGenerator设置对应的前缀内容;
* 通过 我们自定义的前缀类来设置对应缓存中key的前缀内容;
* http://localhost:8080/prefixKey/getSimpleString1?userName=candy
* 然后在对应的缓存中设置的 key是
* prefix:PrefixKeyController.getSimpleString1:candy
* @author khy
* @createTime 2020年11月3日上午10:02:01
* @param userName
* @return
*/
@RequestMapping("/getSimpleString1")
@Cacheable(cacheManager="redisCacheManager" ,value="khy",keyGenerator="prefixKeyGenerator")
public String getSimpleString1(String userName){
System.out.println("getSimpleString 进入方法执行了"+userName);
return "返回的userName="+userName;
}
@RequestMapping("/getSimpleString1")
@Cacheable(cacheManager="redisCacheManager" ,value="khy",keyGenerator="prefixKeyGenerator")
public String getSimpleString1(String userName){
System.out.println("getSimpleString 进入方法执行了"+userName);
return "返回的userName="+userName;
}
/**
* 带有多个参数的通过prefixKeyGenerator来拼接key的
* 请求 http://localhost:8080/prefixKey/getSimpleString2?userName=candy&password=123
* redis设置的缓存 prefix:PrefixKeyController.getSimpleString2:SimpleKey [candy,123]
*
* 请求 http://localhost:8080/prefixKey/getSimpleString2
* redis设置的缓存 prefix:PrefixKeyController.getSimpleString2:SimpleKey [null,null]
*
* 请求 http://localhost:8080/prefixKey/getSimpleString2?userName=candy
* redis设置的缓存 prefix:PrefixKeyController.getSimpleString2:SimpleKey [candy,null]
*
* @author khy
* @createTime 2020年11月3日上午10:17:58
* @param userName
* @param password
* @return
*/
@RequestMapping("/getSimpleString2")
@Cacheable(cacheManager="redisCacheManager" ,value="khy",keyGenerator="prefixKeyGenerator")
public String getSimpleString2(String userName,String password){
System.out.println("getSimpleString2 进入方法执行了"+userName+"password="+password);
return "返回的userName="+userName;
}
如果请求参数是一个对象的话 则需要toString()需要重写方法参数对象
/**
* 请求参数是指定的对象内容;
* 但是如果不对UserEntity 处理的话生成的的 key是带有
* prefix:PrefixKeyController.getSimpleEntity:com.khy.entity.UserEntity@e1475c6
* 内存地址的;这样每次请求都不是同一个是有问题需要将对象的toString()方法重写
* return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
*
* 请求的是http://localhost:8080/prefixKey/getSimpleEntity
* prefix:PrefixKeyController.getSimpleEntity:UserEntity[userName=<null>,password=<null>,id=<null>,age=<null>,createTime=<null>]
*
* 请求的是http://localhost:8080/prefixKey/getSimpleEntity?userName=candy&password=123
* prefix:PrefixKeyController.getSimpleEntity:UserEntity[userName=candy,password=123,id=<null>,age=<null>,createTime=<null>]
* @author khy
* @createTime 2020年11月3日上午10:25:46
* @param userEntity
* @return
*/
@RequestMapping("/getSimpleEntity")
@Cacheable(cacheManager="redisCacheManager" ,value="khy",keyGenerator="prefixKeyGenerator")
public UserEntity getSimpleEntity(UserEntity userEntity){
UserEntity cacheEntity = new UserEntity();
cacheEntity.setId(1);
cacheEntity.setAge(10);
cacheEntity.setUserName("小康康");
cacheEntity.setPassword("password123");
cacheEntity.setCreateTime(new Date());
System.out.println("getSimpleEntity 进入方法内容");
return cacheEntity;
}
public class UserEntity implements Serializable{
@Override
public String toString() {
try {
return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
} catch (Exception var2) {
var2.printStackTrace();
return super.toString();
}
}
}
6.@CachePut
不管缓存中是否有值都会将当前返回值更新到缓存中去
/**
* @CachePut 注解是不管缓存里面有没有都会执行当前方法并且将结果返回设置到缓存中去
* 每次请求访问 http://localhost:8080/prefixKey/getSimpleEntity2?userName=candy&password=123
* 不管缓存里面是否包含key 都会执行下面的方法并且设置返回值内更新到缓存中
* prefix:PrefixKeyController.getSimpleEntity2:UserEntity[userName=candy,password=123,id=<null>,age=<null>,createTime=<null>]
* @author khy
* @createTime 2020年11月3日上午10:54:22
* @param userEntity
* @return
*/
@RequestMapping("/getSimpleEntity2")
@CachePut(cacheManager="redisCacheManager" ,value="khy",keyGenerator="prefixKeyGenerator")
public UserEntity getSimpleEntity2(UserEntity userEntity){
UserEntity cacheEntity = new UserEntity();
cacheEntity.setId(1);
cacheEntity.setAge(10);
cacheEntity.setUserName("小康康");
cacheEntity.setPassword("password123");
cacheEntity.setCreateTime(new Date());
System.out.println("getSimpleEntity2 进入方法内容");
return cacheEntity;
}
7.@CacheEvict
执行当前方法会将指定的缓存中的key值全部清空
清空缓存的原理主要是根据 注解属性中的value指定的;
主要观察我们在@Cacheable 注解中指定value 之后会在缓存中生成一个zset结构的缓存,
每当调用方法请求参数不同都会多一对key-value的缓存值同时zset中也会缓存对应的key;
所以清空缓存的原理就是从zset中获取所有要清空缓存的key然后清除之后将zset缓存也清空
/**
* 清空缓存中的数据主要和value值有关系,也就是在@Cacheable注解 value属性设置了khy
* 则在缓存中生成一个zset格式的记录里面的所有的key; 然后清除的时间是根据zset中到的元素
* 的所有元素进行删除也就是不管该方法userName传递的是不是上面方法传递的都会被清空
* 方法中的value可以指定多个key
* @author khy
* @createTime 2020年11月18日上午10:34:21
* @param userName
* @return
*/
@RequestMapping("/clearSimple")
@CacheEvict(cacheManager="redisCacheManager" ,value="khy",allEntries=true)
public UserEntity clearSimple(String userName){
UserEntity cacheEntity = new UserEntity();
cacheEntity.setId(1);
cacheEntity.setAge(10);
cacheEntity.setUserName("小康康");
cacheEntity.setPassword("password123");
cacheEntity.setCreateTime(new Date());
System.out.println("清空缓存功能方法已执行");
return cacheEntity;
}
总结
一上就是@Cacheable和相关注解的使用;其实除了觉得比较方便;一般项目中缓存也不会这么使用;都会有同一个工具类生成,而且都是根据我们指定数据结构来生成的.而不是这种都当成key-value来处理的;