zoukankan      html  css  js  c++  java
  • 注解实现SpringCache自定义失效时间

    注解实现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来替代等,后面有时间再优化实践吧。

  • 相关阅读:
    在Centos 7下编译openwrt+njit-client
    开博随笔
    Chapter 6. Statements
    Chapter 4. Arrays and Pointers
    Chapter 3. Library Types
    Chapter 2.  Variables and Basic Types
    关于stm32不常用的中断,如何添加, 比如timer10 timer11等
    keil 报错 expected an identifier
    案例分析 串口的地不要接到电源上 会烧掉
    案例分析 CAN OPEN 调试记录 进度
  • 原文地址:https://www.cnblogs.com/bartggg/p/12996651.html
Copyright © 2011-2022 走看看