1.说明
因为原生的@Cacheable 注解无法声明缓存时间,所以在这里可以自定义一个缓存注解。在声明缓存注解的过程中使用了redis,因为在上一篇文章中已经说明了Spring Boot如何整合redis组件了,所以在此就不做过多的说明了。
2.步骤
1.声明@RedisCache注解
/**
* @description: redis缓存注解
* @author: xiaYZ
* @createDate: 2022/5/24
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RedisCache {
//缓存名称
String value() default "";
//缓存的键名
String[] key() default {};
//过期时间,默认1小时,单位秒
int expiredTime() default 3600;
}
2.针对@RedisCache注解进行说明和预处理
/**
* @author 10412
*/
@Component
@Aspect
public class RedisCacheAspect {
/**
* redis操作工具类
*/
@Resource
private RedisUtil redisUtil;
/**
* redission分布式锁
*/
@Resource
private RedissonClient redissonClient;
@Around("@annotation(com.fwy.common.annotation.redis.RedisCache)")
public Object cacheAroundAdvice(ProceedingJoinPoint point) throws Throwable {
// 获得连接点参数
Object[] args = point.getArgs();
// 声明一个对象Object,为方法的返回结果
Object result = null;
// 通过反射获得原始方法信息
MethodSignature signature = (MethodSignature)point.getSignature();
Type returnType = signature.getMethod().getGenericReturnType();
RedisCache redisCache = signature.getMethod().getAnnotation(RedisCache.class);
//获取缓存名称
String key = redisCache.value();
//获取缓存key
String[] keys = redisCache.key();
//获取缓存时间单位s
int expireTime = redisCache.expiredTime();
// 根据注解拼接缓存key
if (keys.length != 0) {
key += ":" + StringUtils.join(keys,":");
}
if(args.length != 0){
key += ":" + StringUtils.join(args,":");
}
// 缓存代码
result = cacheHit(returnType, key);
// 表示缓存不为空,则直接返回数据
if (result != null){
return result;
}
// 使用redisson获得分布式锁
RLock lock = redissonClient.getLock(key + ":lock");
Properties prop = new Properties();
try {
// 成功拿到分布式锁的,可以查询db
boolean b = lock.tryLock(100, 10000, TimeUnit.SECONDS);
if(b){
// 执行连接点方法,查询db
result = point.proceed(args);
// 如果查询不到数据,将空对象放入缓存,防止缓存穿透
if(result == null){
redisUtil.set(key, JSON.toJSONString(new Object()),expireTime);
return null;
}
// 查询到数据后同步缓存返回结果
redisUtil.set(key, JSON.toJSONString(result),expireTime);
// 返回结果给原始方法
return result;
}else {
// 如果没有拿到分布式锁,那么说明已经有人查数据库了,当前执行的线程直接取缓存里面拿其他线程已经存入的数据就行了
// 等待其他线程放入数据
Thread.sleep(1000);
return cacheHit(returnType,key);
}
} catch (Throwable throwable) {
throwable.printStackTrace();
}finally {
if (lock.isLocked()){
lock.unlock();
}
}
// 返回原方法需要的的结果
return result;
}
/***
* 查询缓存中的key
* @param returnType
* @param key
* @return
*/
private Object cacheHit(Type returnType, String key) {
Object result = null;
String cache = (String)redisUtil.get(key);
if(StringUtils.isNotBlank(cache)){
result = JSON.parseObject(cache, returnType);
}
return result;
}
}
3.redis序列化处理
@Configuration
@EnableCaching
public class RedisConfig {
@Bean
public KeyGenerator wiselyKeyGenerator() {
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();
}
};
}
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
//序列号key value
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
//设置默认缓存过期时间
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
// 默认没有特殊指定的,,1天
.entryTtl(Duration.ofHours(1))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
.disableCachingNullValues();
return RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
}
}
4.redis缓存工具类
5.使用
//value缓存键值,expiredTime缓存时间
@Override
@RedisCache(value = "questionBank",expiredTime = 60*60*24*7)
public List<QuestionBank> findAllQuestionBank() {
List<QuestionBank> allQuestionBank = studyDao.findAllQuestionBank();
allQuestionBank.forEach(question ->{
question.setOptions(question.getOptions().replaceAll("\n",""));
});
return allQuestionBank;
}