使用
- 引入pom文件
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
- 添加注解
@EnableCaching
注解使用
注解参数含义
- value . 缓存的名称。必须指定至少一个。和cacheNames 一样
- cacheNames 缓存的名称。必须指定至少一个。和value 一样
- key
缓存的key,可以为空。如果指定要按SPEL表达式编写。不指定会按照入参自行组合
- cacheManager 指定管理器
- condition
缓存的条件,可以为空,使用SPEL表达式编写,返回true或者false,true表示存入缓存
@Cacheable
添加缓存
使用在方法上,指定cacheNames 意思是返回值存入的缓存名称。下次有请求则先去缓存中查看是否有key,key可以自行指定,如果不指定会判断所有入参,如果不同就认为是不同的key。
如果不指定key,则默认全部参数作为key。如果是自定义对象类型,判断不了需要自定义KeyGenerator
@Component
public class MyGenerator implements KeyGenerator{
@override
public Object generate(object o, Method method, object... objects){
}
}
@CacheEvict
清除缓存
用法和cacheable一样,只是删除缓存
@CachePut
更新缓存
每次都会调用方法,把返回值缓存起来
@cacheManager
管理cache 的类。可以配置多个,配置多个的时候需要在默认的bean上添加@primary
@Bean
@Primary
public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
//初始化一个RedisCacheWriter
RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory);
//设置CacheManager的值序列化方式为json序列化
RedisSerializer<Object> jsonSerializer = new GenericJackson2JsonRedisSerializer();
RedisSerializationContext.SerializationPair<Object> pair = RedisSerializationContext.SerializationPair.fromSerializer(jsonSerializer);
//设置默认超过时期是1天
RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(pair)
.entryTtl(Duration.ofDays(1));
//初始化RedisCacheManager
return new RedisCacheManager(redisCacheWriter, defaultCacheConfig);
}
@bean
public ConcurrentMapCacheMamager cacheManager(){
return new ConcurrentMapCacheManager();
}
原理
SpringCache包含两个顶级接口,Cache(缓存)和CacheManager(缓存管理器),顾名思义,用CacheManager去管理一堆Cache。
@Cacheable 中 指定的 cacheNames 其实就是 指向了一个个实现了 Cache 接口的类,我们也可以自定义MyCache 实现 Cache 接口然后使用。
RedisCacheManager
存储情况在redisCache中体现。
cacheWriter中获得存在Redis中的存储的key
defaultrediscachewriter 中是真正和redis交互的类
存储在redis中的key格式为 cacheName::key
.
自定义redis过期时间
在创建rediscachemanager 的时候,还可以传入一个config,是特定cacheName 对应的配置文件
Map<string,redisCacheConfiguration> conf;
new RedisCacheManager(redisCacheWriter, defaultCacheConfig,conf);
思路: 这样还是属于硬编码。想要达到的效果是在 @cacheable 的时候直接能够指定过期时间。
通过利用cachename的方式,把过期时间写到cachename里面,在生成配置文件
代码实现
在 @cacheable 中定义名称为 @Cacheable(cacheNames = "caheame#10s")
用#分割,然后 s m d 设置
package com.meiya.config;
import com.alibaba.fastjson.serializer.MapSerializer;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.text.DecimalFormat;
import java.time.Duration;
import java.util.*;
import java.util.stream.Collectors;
@Configuration
public class CacheConfig {
@Autowired
private ApplicationContext applicationContext;
@Autowired
private RedisConnectionFactory factory;
@Bean
@Primary
RedisCacheManager redisCacheManager() {
final RedisCacheWriter writer = RedisCacheWriter.nonLockingRedisCacheWriter(factory);
final GenericJackson2JsonRedisSerializer jackson = new GenericJackson2JsonRedisSerializer();
final RedisSerializationContext.SerializationPair<Object> pair = RedisSerializationContext.SerializationPair
.fromSerializer(jackson);
final RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.serializeValuesWith(pair).entryTtl(Duration.ofSeconds(30));
final Map<String, RedisCacheConfiguration> conf = getConf();
System.out.println(conf);
return new RedisCacheManager(writer, config, conf);
}
@Bean
public ConcurrentMapCacheManager cacheManager() {
ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
return cacheManager;
}
private Map<String, RedisCacheConfiguration> getConf() {
final Map<String, Object> beansWithAnnotation = applicationContext.getBeansWithAnnotation(Component.class);
final List<CacheMap> collect = beansWithAnnotation.entrySet().stream().flatMap(entry -> {
try {
final Object value = entry.getValue();
// 获得原本的class名字,spring代理的都是后面有$$直接截取即可
String className = "";
if (value.getClass().getName().contains("$")){
className = value.getClass().getName().
substring(0, value.getClass().getName().indexOf("$"));
}else{
className = value.getClass().getName();
}
// 获得原始的字节码文件,如果被spring 代理之后,方法上会获取不到注解信息
final Method[] methods = Class.forName(className)
.getDeclaredMethods();
return Arrays.stream(methods).flatMap(method -> {
final Cacheable annotation = method.getAnnotation(Cacheable.class);
if (annotation == null) {
return null;
}
return Arrays.stream(annotation.cacheNames()).map(data -> {
if (data.contains("#")) {
// 包含自定义日期
final String[] split = data.split("#");
if (split.length != 2) {
return null;
}
final CacheMap cacheMap = new CacheMap();
cacheMap.setName(data);
final String s = split[1];
final int time = Integer.parseInt(s.substring(0, s.length() - 1));
if (s.endsWith("s")) {
cacheMap.setTtl(Duration.ofSeconds(time));
} else if (s.endsWith("m")) {
cacheMap.setTtl(Duration.ofMinutes(time));
} else if (s.endsWith("d")) {
cacheMap.setTtl(Duration.ofDays(time));
}
return cacheMap;
}
return null;
}).filter(Objects::nonNull);
});
} catch (Exception e) {
System.out.println("异常");
return null;
}
}).collect(Collectors.toList());
return collect.stream().collect(Collectors.toMap(CacheMap::getName, p -> {
final GenericJackson2JsonRedisSerializer jackson = new GenericJackson2JsonRedisSerializer();
final RedisSerializationContext.SerializationPair<Object> pair = RedisSerializationContext.SerializationPair
.fromSerializer(jackson);
return RedisCacheConfiguration.defaultCacheConfig()
.serializeValuesWith(pair).entryTtl(p.getTtl());
}, (key1, key2) -> key2));
}
class CacheMap {
private String name;
private Duration ttl;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Duration getTtl() {
return ttl;
}
public void setTtl(Duration ttl) {
this.ttl = ttl;
}
}
}