注解实现SpringCache自定义失效时间

SpringCache是一个很方便的缓存框架,但是官方提供的缓存的配置只有全局的缓存失效时间,没有针对某个命名空间做配置,因为工作上业务的关系需要针对某一个缓存做单独的控制,所有想了个办法来实现。大概分为以下步骤:

1)自定义注解

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.TimeUnit;
/**
* 缓存失效的注解,目前只支持在类级别上有效
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface CacheExpire {
/**
* 失效时间,默认是60
* @return
*/
public long ttl() default 60L; /**
* 单位,默认是秒
* @return
*/
public TimeUnit unit() default TimeUnit.SECONDS;
}

2)CacheManagerHelper获得注解的值

import com.spboot.utils.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component; import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit; /**
* CacheManager的辅助类
*/
@Component
public class CacheManagerHelper implements ApplicationContextAware { private static ApplicationContext applicationContext;
private static Map<String , Duration> CACHE_DURATION = new HashMap<>(); @Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
CacheManagerHelper.applicationContext = applicationContext;
} /**
* 根据cacheName获得对应的duration值
* @param name
* @return
*/
public static Duration getByKey(String name) {
return findAllCacheBean().get(name);
} /**
* 找到所有的被 @CacheConfig 和 @CacheExpire 修饰的类对象
*/
public static Map<String , Duration> findAllCacheBean() {
if(CACHE_DURATION.size() == 0) {
Map<String, Object> beansWithAnnotation = applicationContext.getBeansWithAnnotation(CacheConfig.class);
if(beansWithAnnotation != null && beansWithAnnotation.size() > 0) {
for (Map.Entry<String, Object> entry : beansWithAnnotation.entrySet()) {
Object proxyObject = entry.getValue(); // 代理类
Object realObject = BeanUtils.getTarget(proxyObject); //获得真实的对象
CacheExpire cacheExpire = realObject.getClass().getAnnotation(CacheExpire.class);
if(null != cacheExpire) {
CacheConfig cacheConfig = realObject.getClass().getAnnotation(CacheConfig.class);
String[] cacheNames = cacheConfig.cacheNames();
long convert = TimeUnit.SECONDS.convert(cacheExpire.ttl(), cacheExpire.unit());
Duration duration = Duration.ofSeconds(convert);
for (String cacheName : cacheNames) {
CACHE_DURATION.put(cacheName, duration);
}
}
}
}
}
return CACHE_DURATION;
}
}

3)修改源码org.springframework.data.redis.cache.RedisCache

修改这里是为了改变每次存储之前redis的key的ttl值,通过上面自定义的CacheManagerHelper来获得。

修改源码位置:

  • org.springframework.data.redis.cache.RedisCache#put
  • org.springframework.data.redis.cache.RedisCache#putIfAbsent
  • 添加的方法:
    • getDuration(java.lang.String, org.springframework.data.redis.cache.RedisCacheConfiguration)

因为代码太长,只放出了被修改过的代码,其余的保持不变:

/**
* 如果该命名空间使用了@CacheExpire注解就是用自定义的失效时间,否则使用默认的
* @param name
* @param cacheConfiguration
* @return
*/
private Duration getDuration(String name, RedisCacheConfiguration cacheConfiguration) {
Duration duration = CacheManagerHelper.getByKey(name);
if(null != duration) { // 如果当前命名空间配置了自定义失效时间,使用配置值
return duration;
}
return cacheConfig.getTtl(); // 否则使用全局的配置值
} /*
* (non-Javadoc)
* @see org.springframework.cache.Cache#put(java.lang.Object, java.lang.Object)
*/
@Override
public void put(Object key, @Nullable Object value) { Object cacheValue = preProcessCacheValue(value); if (!isAllowNullValues() && cacheValue == null) { throw new IllegalArgumentException(String.format(
"Cache '%s' does not allow 'null' values. Avoid storing null via '@Cacheable(unless=\"#result == null\")' or configure RedisCache to allow 'null' via RedisCacheConfiguration.",
name));
} // 修改的
cacheWriter.put(name, createAndConvertCacheKey(key), serializeCacheValue(cacheValue), getDuration(name, cacheConfig));
// 默认的
// cacheWriter.put(name, createAndConvertCacheKey(key), serializeCacheValue(cacheValue), cacheConfig.getTtl());
} /*
* (non-Javadoc)
* @see org.springframework.cache.Cache#putIfAbsent(java.lang.Object, java.lang.Object)
*/
@Override
public ValueWrapper putIfAbsent(Object key, @Nullable Object value) { Object cacheValue = preProcessCacheValue(value); if (!isAllowNullValues() && cacheValue == null) {
return get(key);
} // 修改后的
byte[] result = cacheWriter.putIfAbsent(name, createAndConvertCacheKey(key), serializeCacheValue(cacheValue), getDuration(name, cacheConfig));
// 默认的
// byte[] result = cacheWriter.putIfAbsent(name, createAndConvertCacheKey(key), serializeCacheValue(cacheValue),
// cacheConfig.getTtl()); if (result == null) {
return null;
} return new SimpleValueWrapper(fromStoreValue(deserializeCacheValue(result)));
}

4)全局redis cache config

import java.time.Duration;

import org.springframework.cache.annotation.EnableCaching;
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.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer; import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper; @Configuration
@EnableCaching // 开启spring的缓存
public class CacheConfig { /**
* 自定义得缓存管理器
* @param redisConnectionFactory
* @return
*/
@Primary
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) { //初始化一个RedisCacheWriter
RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory); // key 序列化方式
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// value的序列化机制
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = jackson2JsonRedisSerializer(); // 配置
RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(1)) // 默认1个小时失效时间
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(stringRedisSerializer)) // 设置 k v 序列化机制
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)); //初始化RedisCacheManager
RedisCacheManager cacheManager = new RedisCacheManager(redisCacheWriter, defaultCacheConfig); return cacheManager;
} Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer() {
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
return jackson2JsonRedisSerializer;
} }

5)使用注解

假设在一个controller使用注解,例如:

@CacheExpire(ttl = 10, unit = TimeUnit.SECONDS) // 自定义注解,10秒钟就过期
@CacheConfig(
cacheNames = "testApiService")
@RestController
public class TestApi {
@Cacheable
@GetMapping("/api/redis")
public Map<String,String> data() {
Map<String,String> map = new HashMap<String, String>();
map.put("k1", "v1");
map.put("k2", "v2");
map.put("k3", "v3");
return map;
}
}

具体的代码地址

如此一来就实现了使用注解控制缓存失效时间,这里还有优化的空间,比如注解精细到方法粒度的控制,使用aop来替代等,后面有时间再优化实践吧。

注解实现SpringCache自定义失效时间的更多相关文章

  1. java 日志脱敏框架 sensitive-v0.0.4 系统内置常见注解,支持自定义注解

    项目介绍 日志脱敏是常见的安全需求.普通的基于工具类方法的方式,对代码的入侵性太强.编写起来又特别麻烦. 本项目提供基于注解的方式,并且内置了常见的脱敏方式,便于开发. 特性 基于注解的日志脱敏. 可 ...

  2. 深入JAVA注解(Annotation):自定义注解 (转)

    原文出自:http://blog.csdn.net/yjclsx/article/details/52101922 一.基础知识:元注解 要深入学习注解,我们就必须能定义自己的注解,并使用注解,在定义 ...

  3. Java日志脱敏框架 sensitive-v0.0.4 系统内置常见注解,支持自定义注解

    项目介绍 日志脱敏是常见的安全需求.普通的基于工具类方法的方式,对代码的入侵性太强.编写起来又特别麻烦. 本项目提供基于注解的方式,并且内置了常见的脱敏方式,便于开发. 特性 基于注解的日志脱敏. 可 ...

  4. Java注解Annotation与自定义注解详解

    Java注解简介 开发中经常使用到注解,在项目中也偶尔会见到过自定义注解,今天就来探讨一下这个注解是什么鬼,以及注解的应用场景和如何自定义注解. 下面列举开发中常见的注解 @Override:用于标识 ...

  5. SpringCache自定义过期时间及自动刷新

    背景前提 阅读说明(十分重要) 对于Cache和SpringCache原理不太清楚的朋友,可以看我之前写的文章:Springboot中的缓存Cache和CacheManager原理介绍 能关注Spri ...

  6. Java注解教程:自定义注解示例,利用反射进行解析

    Java注解能够提供代码的相关信息,同时对于所注解的代码结构又没有直接影响.在这篇教程中,我们将学习Java注解,如何编写自定义注解,注解的使用,以及如何使用反射解析注解. 注解是Java 1.5引入 ...

  7. Java注解教程及自定义注解

    Java注解提供了关于代码的一些信息,但并不直接作用于它所注解的代码内容.在这个教程当中,我们将学习Java的注解,如何定制注解,注解的使用以及如何通过反射解析注解. Java1.5引入了注解,当前许 ...

  8. 160621、Java注解教程及自定义注解

    Java注解提供了关于代码的一些信息,但并不直接作用于它所注解的代码内容.在这个教程当中,我们将学习Java的注解,如何定制注解,注解的使用以及如何通过反射解析注解. Java1.5引入了注解,当前许 ...

  9. 注解:@interface 自定义注解的语法

      自定义注解: 使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口,由编译程序自动完成其他细节.在定义注解时,不能继承其他的注解或接口 ...

随机推荐

  1. Nginx初步入门

    1.Nginx介绍 官网:nginx.org Nginx ("engine x") 是一个开源的.支持高性能.高并发的WWW服务和代理服务软件. 它是由俄罗斯人IgorSysoev ...

  2. Python操作MongoDB代码示例

    import pymongo #pip install pymongo安装python操作mongodb的模块 myclient=pymongo.MongoClient(host='127.0.0.1 ...

  3. 【K8S】基于Docker+K8S+GitLab/SVN+Jenkins+Harbor搭建持续集成交付环境(环境搭建篇)

    写在前面 最近在 K8S 1.18.2 版本的集群上搭建DevOps环境,期间遇到了各种坑.目前,搭建环境的过程中出现的各种坑均已被填平,特此记录,并分享给大家! 服务器规划 IP 主机名 节点 操作 ...

  4. SAP Basis 客户端拷贝

    一.版本.系统配置信息 软件 版本信息 操作系统 AIX 6.1 SAP ECC6.0 SAP_BASIS 7.00SP15kernel 720 600 oracle 11.2.0.2.0 DB大小 ...

  5. ESlint中console.log报错问题

    ESlint中console.log报错问题 由于ESlint规范化,导致console.log的使用也会报错,下面是设置允许console.log控制台输出 描述:打开 package.json 文 ...

  6. 【Java】面试官灵魂拷问:if语句执行完else语句真的不会再执行吗?

    写在前面 最近跳槽找工作的朋友确实不少,遇到的面试题也是千奇百怪,这不,一名读者朋友面试时,被面试官问到了一个直击灵魂的问题:if 语句执行完else语句真的不会再执行吗?这个奇葩的问题把这名读者问倒 ...

  7. 用开源软件TrinityCore在Debian 10上搭建魔兽世界8.3.0.34220的服务器

    用开源软件TrinityCore在Debian 10上搭建魔兽世界8.3.0.34220的服务器 TrinityCore是魔兽世界(World of Warcraft)的开源的服务端.目前支持魔兽的3 ...

  8. Java基础语法--java中字符串比较中的坑点

    Java 中两个字符串比较大小,可以有两种方式判定,要根据需求选择 == 判定,比较的是两个字符串的内存地址,地址相同则判定为true:反之则反 equals() 判定,比较的是两个字符串的内容,内容 ...

  9. HomeLede 2020.5.27更新 UPnP+NAS+多拨+网盘+DNS优化+帕斯沃/Clash 无缝集成+软件包

    交流群:QQ 1030484865 电报 t.me/t_homelede   固件说明 基于Lede OpenWrt R2020.5.20版本(源码截止2020.5.27)及若干自行维护的软件包 结合 ...

  10. ReentrantLock解析及源码分析

    本文结构 Tips:说明一部分概念及阅读源码需要的基础内容 ReentrantLock简介 公平机制:对于公平机制和非公平机制进行介绍,包含对比 实现:Sync源码解析额,公平和非公平模式的加锁.解锁 ...