Spring Cache+redis自定义缓存过期时间

在使用Spring cache以注解+AOP的方式封装了操作的缓存的统一处理。注解Cacheable,先查询缓存,如果没有值,执行方法把返回结果保存到缓存。CachePut执行方法,把返回的结果保存的缓存。CacheEvict删除缓存。在设置缓存的时候,提供了默认的失效时间的参数配置,但是没有提供用户自定义失效时间的设置,该功能需要用户自定义实现。以redis缓存自定义失效时间为例,讲解下实现方案。

在设置CacheManager时,有设置默认的失效时间属性,代码如下:

    @Bean
    @ConditionalOnMissingBean(value = CacheManager.class)
    public CacheManager cacheManager(@Autowired RedisConnectionFactory redisConnectionFactory) {

        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
        // 设置缓存的默认过期时间
        config = config.entryTtl(Duration.ofHours(1))
                // 不缓存空值
                .disableCachingNullValues();
        RedisCacheManager cacheManager = new ExpireTimeRedisCacheManager(
                RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory), config);
        return cacheManager;
    }

在设置缓存的注解,Cacheable和CachePut都没有缓存失效时间的属性。如果实现自定义key失效时间需要用户自定义。分析源码AbstractCacheManager得知,在从缓存获取数据的时,调用方法Cache getCache(String name),如果缓存中没有数据,则调用Cache getMissingCache(String name)方法获取缓存数据。代码如下:

public abstract class AbstractCacheManager implements CacheManager, InitializingBean {

    ......

	@Override
	@Nullable
	public Cache getCache(String name) {
		// Quick check for existing cache...
		Cache cache = this.cacheMap.get(name);
		if (cache != null) {
			return cache;
		}

		// The provider may support on-demand cache creation...
		Cache missingCache = getMissingCache(name);
		if (missingCache != null) {
			// Fully synchronize now for missing cache registration
			synchronized (this.cacheMap) {
				cache = this.cacheMap.get(name);
				if (cache == null) {
					cache = decorateCache(missingCache);
					this.cacheMap.put(name, cache);
					updateCacheNames(name);
				}
			}
		}
		return cache;
	}

	......

	/**子类实现
	 */
	@Nullable
	protected Cache getMissingCache(String name) {
		return null;
	}

}

在源码中RedisCacheManager,实现方法getMissingCache的逻辑可知,生成缓存由方法edisCache createRedisCache(String name, @Nullable RedisCacheConfiguration cacheConfig)实现。代码如下:

public class RedisCacheManager extends AbstractTransactionSupportingCacheManager {

    ......

	@Override
	protected RedisCache getMissingCache(String name) {
		return allowInFlightCacheCreation ? createRedisCache(name, defaultCacheConfig) : null;
	}

    ......

	protected RedisCache createRedisCache(String name, @Nullable RedisCacheConfiguration cacheConfig) {
		return new RedisCache(name, cacheWriter, cacheConfig != null ? cacheConfig : defaultCacheConfig);
	}

    ......


}

通过源码分析得知,实现自定义失效时间需要在执行createRedisCache方法时,重新设置失效时间配置即可。我实现的方案是在Cacheable和CachePut设置缓存名称时,设置自定义时间的配置,缓存名称的示例值为users#10_SECONDS,缓存名称#失效时间值_失效时间单位,#为缓存名称与失效时间配置的分隔符;_为失效时间与失效时间单位的分隔符。代码如下:

@Configuration
@Slf4j
@EnableCaching
@AutoConfigureAfter(RedisAutoConfiguration.class)
public class RedisCacheAutoConfiguration extends CachingConfigurerSupport {

    /**
     * redis FastJson序列化
     */
    @Bean
    @ConditionalOnMissingBean(value = RedisSerializer.class)
    public RedisSerializer<Object> redisSerializer() {
        return new GenericFastJsonRedisSerializer();
    }

    /**
     * 设置自定义序列化的RedisTemplate
     *
     * @param redisConnectionFactory
     * @param redisSerializer        序列化
     * @return
     */
    @Bean
    @ConditionalOnMissingBean(RedisTemplate.class)
    public RedisTemplate<String, Object> redisTemplate(@Autowired RedisConnectionFactory redisConnectionFactory,
                                                       @Autowired RedisSerializer<Object> redisSerializer) {

        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        // value,hash value设置FastJson序列化
        template.setHashValueSerializer(redisSerializer);
        template.setValueSerializer(redisSerializer);
        // key,hash key使用String序列化
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(Charset.forName("UTF-8"));
        template.setHashKeySerializer(stringRedisSerializer);
        template.setKeySerializer(stringRedisSerializer);
        return template;
    }

    /**
     * 配置 spring cache manager RedisCacheManager
     */
    @Bean
    @ConditionalOnMissingBean(value = CacheManager.class)
    public CacheManager cacheManager(@Autowired RedisConnectionFactory redisConnectionFactory) {

        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
        // 设置缓存的默认过期时间
        config = config.entryTtl(Duration.ofHours(1))
                // 不缓存空值
                .disableCachingNullValues();
        RedisCacheManager cacheManager = new ExpireTimeRedisCacheManager(
                RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory), config);
        return cacheManager;
    }

    /**
     * 可以自动设置失效时间的RedisCacheManager
     * 失效时间设置规则 @Cacheable(cacheNames = "users#10_SECONDS")
     * # 为cacheNames 与自定义失效时间的分隔符
     * _ 为失效时间值与单位的分隔符
     *
     * @Author iloveoverfly
     * @Date 2020/2/13 19:26
     **/
    @Slf4j
    private static class ExpireTimeRedisCacheManager extends RedisCacheManager {

        private ExpireTimeRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) {
            super(cacheWriter, defaultCacheConfiguration);
        }

        /**
         * 生成缓存配置
         *
         * @param name        设置的缓存名称(完整配置的例子users#10_SECONDS)
         * @param cacheConfig redis缓存配置
         * @return
         */
        @Override
        protected RedisCache createRedisCache(String name, RedisCacheConfiguration cacheConfig) {

            if (StringUtils.isBlank(name)) {
                return super.createRedisCache(name, cacheConfig);
            }
            String[] nameSplits = name.split(NAME_TIME_CONFIG_SEPARATOR);
            if (ArrayUtils.isNotEmpty(nameSplits) && 1 == nameSplits.length) {

                if (log.isDebugEnabled()) {
                    log.debug("set default ttl millis {}", cacheConfig.getTtl().toMillis());
                }
                return super.createRedisCache(name, cacheConfig);
            }
            String finalName = nameSplits[0];
            String ttlTimeConfig = nameSplits[1];
            String[] ttlTimeConfigSplits = ttlTimeConfig.split(TTL_TIME_SEPARATOR);
            if (ArrayUtils.isEmpty(ttlTimeConfigSplits) || 2 != ttlTimeConfigSplits.length) {

                log.info("ttl time config {} is invalid!", ttlTimeConfig);
                return super.createRedisCache(finalName, cacheConfig);
            }
            try {
                Long time = Long.parseLong(ttlTimeConfigSplits[0]);
                String timeUnitName = ttlTimeConfigSplits[1].toUpperCase();
                TimeUnit timeUnit = TimeUnit.valueOf(timeUnitName);
                Duration ttl = obtainAndAddRandomSecs(time, timeUnit);
                RedisCacheConfiguration newTtlRedisCacheConfiguration = cacheConfig.entryTtl(ttl);
                return super.createRedisCache(finalName, newTtlRedisCacheConfiguration);
            } catch (Exception e) {

                log.warn("obtain ttl time config error!", e);
            }
            return super.createRedisCache(finalName, cacheConfig);
        }

        /**
         * 获取ttl失效时间并且随机增加毫秒数
         *
         * @param time
         * @param timeUnit
         * @return
         */
        private Duration obtainAndAddRandomSecs(Long time, TimeUnit timeUnit) {

            long seconds;
            switch (timeUnit) {
                case SECONDS:
                    seconds = time;
                    break;
                case MILLISECONDS:
                    seconds = TimeUnit.MILLISECONDS.toSeconds(time);
                    break;
                case HOURS:
                    seconds = TimeUnit.HOURS.toSeconds(time);
                    break;
                case MINUTES:
                    seconds = TimeUnit.MINUTES.toSeconds(time);
                    break;
                case DAYS:
                    seconds = TimeUnit.DAYS.toSeconds(time);
                    break;
                default:
                    seconds = TimeUnit.SECONDS.toSeconds(time);
                    break;
            }
            // 随机增加失效秒数,避免同一时间集体失效
            long secondsWithRandom = seconds + RedisConst.EXPIRE_RANDOM.nextInt(10);
            return Duration.ofSeconds(secondsWithRandom);
        }
    }
}
发布了37 篇原创文章 · 获赞 2 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/new_com/article/details/104303855