解决Spring-Cache对redis设置过期时间问题

Spring-Cache因为支持多种缓存,每种缓存过期时间的设置方式无法统一,因此没有给出一个统一的处理方案,但是通过RedisCacheManager的源码可以找到解决方法.

先上一下效果:

    @Cacheable(value = RedisKey.APP+"dashboardApi", cacheManager = "redisCacheManager")
	@CacheExpire(60)
	public List<NewAlertVo> newAlert(Integer type, String year){
      //....略
    }

通过增加@CacheExpire注解来实现设置过期时间.

要实现上述效果,我们需要重写一下RedisCacheManager

public class YourRedisCacheManager extends RedisCacheManager implements ApplicationContextAware, InitializingBean {
	 
    private static final Logger LOGGER = LoggerFactory.getLogger(ConnextRedisCacheManager.class);
 
    private ApplicationContext applicationContext;
 
    private Map<String, RedisCacheConfiguration> initialCacheConfiguration = new LinkedHashMap<>();
 
    /**
     * key serializer
     */
    public static final StringRedisSerializer STRING_SERIALIZER = new StringRedisSerializer();
 
    /**
     * value serializer
     * <pre>
     *     使用 FastJsonRedisSerializer 会报错:java.lang.ClassCastException
     *     FastJsonRedisSerializer<Object> fastSerializer = new FastJsonRedisSerializer<>(Object.class);
     * </pre>
     */
 
    public static final GenericFastJsonRedisSerializer FASTJSON_SERIALIZER = new GenericFastJsonRedisSerializer();
 
    /**
     * key serializer pair
     */
    public static final RedisSerializationContext.SerializationPair<String> STRING_PAIR = RedisSerializationContext
            .SerializationPair.fromSerializer(STRING_SERIALIZER);
    /**
     * value serializer pair
     */
    public static final RedisSerializationContext.SerializationPair<Object> FASTJSON_PAIR = RedisSerializationContext
            .SerializationPair.fromSerializer(FASTJSON_SERIALIZER);
 
    public ConnextRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) {
        super(cacheWriter, defaultCacheConfiguration);
    }
 
    @Override
    public Cache getCache(String name) {
        Cache cache = super.getCache(name);
        return new RedisCacheWrapper(cache);
    }
 
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
 
    @Override
    public void afterPropertiesSet() {
        String[] beanNames = applicationContext.getBeanNamesForType(Object.class);
        for (String beanName : beanNames) {
            final Class clazz = applicationContext.getType(beanName);
            add(clazz);
        }
        super.afterPropertiesSet();
    }
 
    @Override
    protected Collection<RedisCache> loadCaches() {
        List<RedisCache> caches = new LinkedList<>();
        for (Map.Entry<String, RedisCacheConfiguration> entry : initialCacheConfiguration.entrySet()) {
            caches.add(super.createRedisCache(entry.getKey(), entry.getValue()));
        }
        return caches;
    }
 
    private void add(final Class clazz) {
        ReflectionUtils.doWithMethods(clazz, method -> {
            ReflectionUtils.makeAccessible(method);
            CacheExpire cacheExpire = AnnotationUtils.findAnnotation(method, CacheExpire.class);
            if (cacheExpire == null) {
                return;
            }
            Cacheable cacheable = AnnotationUtils.findAnnotation(method, Cacheable.class);
            if (cacheable != null) {
                add(cacheable.cacheNames(), cacheExpire);
                return;
            }
            Caching caching = AnnotationUtils.findAnnotation(method, Caching.class);
            if (caching != null) {
                Cacheable[] cs = caching.cacheable();
                if (cs.length > 0) {
                    for (Cacheable c : cs) {
                        if (cacheExpire != null && c != null) {
                            add(c.cacheNames(), cacheExpire);
                        }
                    }
                }
            } else {
                CacheConfig cacheConfig = AnnotationUtils.findAnnotation(clazz, CacheConfig.class);
                if (cacheConfig != null) {
                    add(cacheConfig.cacheNames(), cacheExpire);
                }
            }
        }, method -> null != AnnotationUtils.findAnnotation(method, CacheExpire.class));
    }
 
    private void add(String[] cacheNames, CacheExpire cacheExpire) {
        for (String cacheName : cacheNames) {
            if (cacheName == null || "".equals(cacheName.trim())) {
                continue;
            }
            long expire = cacheExpire.expire();
            if(cacheExpire.overDay()) {
            	expire=getToTomorrowSeconds(new Date());
            }
            LOGGER.info("cacheName: {}, expire: {}", cacheName, expire);
            if (expire >= 0) {
                // 缓存配置
                RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                        .entryTtl(Duration.ofSeconds(expire))
                        .disableCachingNullValues()
                        // .prefixKeysWith(cacheName)
                        .serializeKeysWith(STRING_PAIR)
                        .serializeValuesWith(FASTJSON_PAIR);
                initialCacheConfiguration.put(cacheName, config);
            } else {
                LOGGER.warn("{} use default expiration.", cacheName);
            }
        }
    }
    
 
    protected static class RedisCacheWrapper implements Cache {
        private final Cache cache;
 
        RedisCacheWrapper(Cache cache) {
            this.cache = cache;
        }
 
        @Override
        public String getName() {
//             LOGGER.info("name: {}", cache.getName());
            try {
                return cache.getName();
            } catch (Exception e) {
                LOGGER.error("getName ---> errmsg: {}", e.getMessage(), e);
                return null;
            }
        }
 
        @Override
        public Object getNativeCache() {
//             LOGGER.info("nativeCache: {}", cache.getNativeCache());
            try {
                return cache.getNativeCache();
            } catch (Exception e) {
                LOGGER.error("getNativeCache ---> errmsg: {}", e.getMessage(), e);
                return null;
            }
        }
 
        @Override
        public ValueWrapper get(Object o) {
//             LOGGER.info("get ---> o: {}", o);
            try {
                return cache.get(o);
            } catch (Exception e) {
                LOGGER.error("get ---> o: {}, errmsg: {}", o, e.getMessage(), e);
                return null;
            }
        }
 
        @Override
        public <T> T get(Object o, Class<T> aClass) {
//             LOGGER.info("get ---> o: {}, clazz: {}", o, aClass);
            try {
                return cache.get(o, aClass);
            } catch (Exception e) {
                LOGGER.error("get ---> o: {}, clazz: {}, errmsg: {}", o, aClass, e.getMessage(), e);
                return null;
            }
        }
 
 
        @Override
        public <T> T get(Object o, Callable<T> callable) {
//             LOGGER.info("get ---> o: {}", o);
            try {
                return cache.get(o, callable);
            } catch (Exception e) {
                LOGGER.error("get ---> o: {}, errmsg: {}", o, e.getMessage(), e);
                return null;
            }
        }
 
        @Override
        public void put(Object o, Object o1) {
             LOGGER.debug("put ---> o: {}, o1: {}", o, o1);
            try {
                cache.put(o, o1);
            } catch (Exception e) {
                LOGGER.error("put ---> o: {}, o1: {}, errmsg: {}", o, o1, e.getMessage(), e);
            }
        }
 
        @Override
        public ValueWrapper putIfAbsent(Object o, Object o1) {
             LOGGER.debug("putIfAbsent ---> o: {}, o1: {}", o, o1);
            try {
                return cache.putIfAbsent(o, o1);
            } catch (Exception e) {
                LOGGER.error("putIfAbsent ---> o: {}, o1: {}, errmsg: {}", o, o1, e.getMessage(), e);
                return null;
            }
        }
 
        @Override
        public void evict(Object o) {
//             LOGGER.info("evict ---> o: {}", o);
            try {
                cache.evict(o);
            } catch (Exception e) {
                LOGGER.error("evict ---> o: {}, errmsg: {}", o, e.getMessage(), e);
            }
        }
 
        @Override
        public void clear() {
//             LOGGER.info("clear");
            try {
                cache.clear();
            } catch (Exception e) {
                LOGGER.error("clear ---> errmsg: {}", e.getMessage(), e);
            }
        }
    }

    
    public static long getToTomorrowSeconds(Date currentTime) {
		// 从一个 Instant和区域ID获得 LocalDateTime实例
		LocalDateTime localDateTime = LocalDateTime.ofInstant(currentTime.toInstant(), ZoneId.systemDefault());
		// 获取第第二天零点时刻的实例
		LocalDateTime toromorrowTime = LocalDateTime.ofInstant(currentTime.toInstant(), ZoneId.systemDefault()).plusDays(1).withHour(0).withMinute(0).withSecond(0).withNano(0);
		// ChronoUnit日期枚举类,between方法计算两个时间对象之间的时间量
		long seconds = ChronoUnit.SECONDS.between(localDateTime, toromorrowTime);
		return seconds;

	}

其中增加了启动就加载所有需要缓存的接口,不需要刻意注掉.还增加了在@CacheExpire注解中加入overDay的参数,表示存到当天结束.大家也可以根据自己的需要自行添加其他需要的内容.

重写了RedisCacheManager我们需要注入到容器中,因此需要写一个config,来继承CachingConfigurerSupport 代码如下:

@Configuration
@EnableCaching
@ConditionalOnProperty(name = "msf.redis.enable", havingValue = "true", matchIfMissing = true)
@Slf4j
public class YourRedisCacheConfig extends CachingConfigurerSupport {

	private final RedisConnectionFactory redisConnectionFactory;

	YourRedisCacheConfig (RedisConnectionFactory redisConnectionFactory) {
		this.redisConnectionFactory = redisConnectionFactory;
	}

	@Bean("redisTemplate")
	public RedisTemplate<String, Object> redisTemplate() {
		RedisTemplate<String, Object> template = new RedisTemplate<>();
		// set key serializer
		StringRedisSerializer serializer = YourRedisCacheManager.STRING_SERIALIZER;
		// 设置key序列化类,否则key前面会多了一些乱码
		template.setKeySerializer(serializer);
		template.setHashKeySerializer(serializer);

		// fastjson serializer
		GenericFastJsonRedisSerializer fastSerializer = YourRedisCacheManager.FASTJSON_SERIALIZER;
		template.setValueSerializer(fastSerializer);
		template.setHashValueSerializer(fastSerializer);
		// 如果 KeySerializer 或者 ValueSerializer 没有配置,则对应的 KeySerializer、ValueSerializer
		// 才使用这个 Serializer
		template.setDefaultSerializer(fastSerializer);

		LettuceConnectionFactory factory = (LettuceConnectionFactory) redisConnectionFactory;
		log.info("redis init...");
		log.info("redis.factory: {}", redisConnectionFactory);
		log.info("redis.host: {}", factory.getHostName());

//        log.info("spring.redis.database: {}", factory.getDatabase());
//        log.info("spring.redis.host: {}", factory.getHostName());
//        log.info("spring.redis.port: {}", factory.getPort());
//        log.info("spring.redis.timeout: {}", factory.getTimeout());
//        log.info("spring.redis.password: {}", factory.getPassword());

		// factory
		template.setConnectionFactory(redisConnectionFactory);
		template.afterPropertiesSet();
		return template;
	}

	@Bean
	@Override
	public KeyGenerator keyGenerator() {
		return (o, method, objects) -> {
			StringBuilder sb = new StringBuilder(32);
			sb.append(o.getClass().getSimpleName());
			sb.append(".");
			sb.append(method.getName());
			if (objects.length > 0) {
				sb.append("#");
			}
			String sp = "";
			for (Object object : objects) {
				sb.append(sp);
				if (object == null) {
					sb.append("NULL");
				} else {
					sb.append(object.toString());
				}
				sp = ".";
			}
			return sb.toString();
		};
	}

	/**
	 * 配置 RedisCacheManager,使用 cache 注解管理 redis 缓存
	 */
//	@Primary
	@Bean(name = "redisCacheManager")
	@Override
	public CacheManager cacheManager() {
		// 初始化一个RedisCacheWriter
		RedisCacheWriter cacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory);

		// 设置默认过期时间:60s
		RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofSeconds(60))
				// .disableCachingNullValues()
				// 使用注解时的序列化、反序列化
				.serializeKeysWith(ConnextRedisCacheManager.STRING_PAIR).serializeValuesWith(ConnextRedisCacheManager.FASTJSON_PAIR);

		return new YourRedisCacheManager(cacheWriter, defaultCacheConfig);
	}

}

这里重写了keyGenerator,用指定的value+方法名+参数名组成key,可以实现同一个方法,不同的入参存不同的key的效果.

并且给CacheManager方法指定bean的名称为redisCacheManager,这样我们在用@Cacheable的时候指定这个manager就可以实现我们需要的效果了.

最后附上@CacheExpire注解的代码:

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface CacheExpire {

	/**
     * expire time, default 60s
     */
    @AliasFor("expire")
    long value() default 60L;

    /**
     * expire time, default 60s
     */
    @AliasFor("value")
    long expire() default 60L;
    
    /**
     * 
     * @Description:如果overDay=true 则存活时间到今天23点59分59秒
     * @return:boolean
     */
    boolean overDay() default false;

注:部分代码参考了互联网上前辈的代码,并非全部原创.

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值