• 注解实现SpringCache自定义失效时间(升级版)


    注解实现SpringCache自定义失效时间(升级版)

    之前做过注解实现自定义失效时间,但是需要重写spring-cache中的RedisCache源码,有些不怎么容易扩展,这里使用自定义的CacheManager、和RedisCache类来实现对应的逻辑:
    旧版本链接

    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获得注解的值

    package com.spboot.config.cache.helper;
    
    import com.spboot.config.cache.RedisCacheCustomize;
    import com.spboot.utils.BeanUtils;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.BeansException;
    import org.springframework.cache.annotation.CacheConfig;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.ApplicationContextAware;
    import org.springframework.data.redis.cache.RedisCache;
    import org.springframework.data.redis.cache.RedisCacheConfiguration;
    import org.springframework.stereotype.Component;
    
    import java.lang.reflect.Field;
    import java.lang.reflect.Modifier;
    import java.time.Duration;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.concurrent.TimeUnit;
    
    /**
     * 修改CacheManager的辅助类 调用时机: execute:314, CacheAspectSupport
     * (org.springframework.cache.interceptor) invoke:61, CacheInterceptor
     * (org.springframework.cache.interceptor) proceed:185,
     * ReflectiveMethodInvocation (org.springframework.aop.framework) 由于 SpringAOP
     * 只能切 Spring 容器管理的类,而调用时机本身就是使用反射来实现的 同是全局使用的是同一个 CacheManager,所以无法使用aop
     */
    @Component
    public class CacheManagerHelper implements ApplicationContextAware {
    
    	private final Logger logger = LoggerFactory.getLogger(CacheManagerHelper.class);
    
    	private static ApplicationContext applicationContext;
    
    	private static Map<String, Duration> CACHE_DURATION = new HashMap<>();
    
    	/** ttl字段 */
    	@Deprecated
    	private static Field CACHE_TTL;
    
    	/** ttl字段的修饰器 */
    	@Deprecated
    	private static Field CACHE_TTL_MODIFIERS;
    	static {
    		// 缓存以提高反射的性能
    		try {
    			CACHE_TTL = RedisCacheConfiguration.class.getDeclaredField("ttl");
    			CACHE_TTL.setAccessible(true);
    			// 获取字段修改器
    			CACHE_TTL_MODIFIERS = CACHE_TTL.getClass().getDeclaredField("modifiers");
    			CACHE_TTL_MODIFIERS.setAccessible(true);
    		} catch (Exception e) {
    		}
    	}
    
    	@Override
    	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    		CacheManagerHelper.applicationContext = applicationContext;
    	}
    
    	/**
    	 * 获得失效时间,不存在就按照默认的失效时间
    	 * @param redisCacheCustomize
    	 * @return
    	 */
    	public static Duration getByCache(RedisCacheCustomize redisCacheCustomize) {
    		Duration duration = getByKey(redisCacheCustomize.getName());
    		if( null == duration) {
    			return redisCacheCustomize.getCacheConfig().getTtl();
    		}
    		return duration;
    	}
    
    	/**
    	 * 根据cacheName获得对应的duration值
    	 * 
    	 * @param name
    	 * @return
    	 */
    	public static Duration getByKey(String name) {
    		return findAllCacheBean().get(name);
    	}
    
    	/**
    	 * 修改redisCache对象中持有的RedisCacheConfiguration的 private final Duration ttl;
    	 * 
    	 * @param redisCache
    	 */
    	@Deprecated
    	public void modifyRedisCacheConfiguration(RedisCache redisCache) {
    		Duration duration = findAllCacheBean().get(redisCache.getName());
    		if (null == duration) {
    			return;
    		}
    		// 找到了就修改
    		modify(redisCache.getCacheConfiguration(), duration);
    	}
    
    	/**
    	 * 修改RedisCacheConfiguration的private final Duration ttl;
    	 * 
    	 * @param cacheConfiguration
    	 * @param duration
    	 */
    	@Deprecated
    	private void modify(RedisCacheConfiguration cacheConfiguration, Duration duration) {
    		try {
    			// 去掉final修饰符
    			CACHE_TTL_MODIFIERS.setInt(CACHE_TTL, CACHE_TTL.getModifiers() & ~Modifier.FINAL);
    			// 修改字段值
    			CACHE_TTL.set(cacheConfiguration, duration);
    			// 添加final修饰符
    			CACHE_TTL_MODIFIERS.setInt(CACHE_TTL, CACHE_TTL.getModifiers() & ~Modifier.FINAL);
    		} catch (Exception e) {
    			logger.error("修改cacheConfiguration的ttl失败,原因:{}", e.getMessage());
    		}
    	}
    
    	/**
    	 * 找到所有的被 @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)自定义RedisCacheCustomize

    package com.spboot.config.cache;
    
    import com.spboot.config.cache.helper.CacheManagerHelper;
    import org.springframework.cache.support.SimpleValueWrapper;
    import org.springframework.core.convert.ConversionService;
    import org.springframework.data.redis.cache.RedisCache;
    import org.springframework.data.redis.cache.RedisCacheConfiguration;
    import org.springframework.data.redis.cache.RedisCacheWriter;
    import org.springframework.util.Assert;
    
    public class RedisCacheCustomize extends RedisCache {
    
        private final String name;
        private final RedisCacheWriter cacheWriter;
        private final RedisCacheConfiguration cacheConfig;
        private final ConversionService conversionService;
    
        protected RedisCacheCustomize(String name, RedisCacheWriter cacheWriter, RedisCacheConfiguration cacheConfig) {
            super(name, cacheWriter, cacheConfig);
            Assert.notNull(name, "Name must not be null!");
            Assert.notNull(cacheWriter, "CacheWriter must not be null!");
            Assert.notNull(cacheConfig, "CacheConfig must not be null!");
    
            this.name = name;
            this.cacheWriter = cacheWriter;
            this.cacheConfig = cacheConfig;
            this.conversionService = cacheConfig.getConversionService();
        }
    
        public RedisCacheConfiguration getCacheConfig() {
            return cacheConfig;
        }
    
        @Override
        public void put(Object key, 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), CacheManagerHelper.getByCache(this));
        }
    
        @Override
        public ValueWrapper putIfAbsent(Object key, Object value) {
            Object cacheValue = preProcessCacheValue(value);
            if (!isAllowNullValues() && cacheValue == null) {
                return get(key);
            }
            byte[] result = cacheWriter.putIfAbsent(name, createAndConvertCacheKey(key), serializeCacheValue(cacheValue),
                    CacheManagerHelper.getByCache(this));
            if (result == null) {
                return null;
            }
            return new SimpleValueWrapper(fromStoreValue(deserializeCacheValue(result)));
        }
    
        private byte[] createAndConvertCacheKey(Object key) {
            return serializeCacheKey(createCacheKey(key));
        }
    
    }
    
    

    4)自定义RedisCacheManagerCustomize

    package com.spboot.config.cache;
    
    import org.springframework.data.redis.cache.RedisCache;
    import org.springframework.data.redis.cache.RedisCacheConfiguration;
    import org.springframework.data.redis.cache.RedisCacheManager;
    import org.springframework.data.redis.cache.RedisCacheWriter;
    
    public class RedisCacheManagerCustomize extends RedisCacheManager {
    
        private final RedisCacheWriter cacheWriter;
        private final RedisCacheConfiguration defaultCacheConfig;
    
        public RedisCacheManagerCustomize(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) {
            super(cacheWriter, defaultCacheConfiguration);
            this.cacheWriter = cacheWriter;
            this.defaultCacheConfig = defaultCacheConfiguration;
        }
    
        @Override
        protected RedisCache createRedisCache(String name, RedisCacheConfiguration cacheConfig) {
          // 使用自己的  RedisCacheCustomize
          return new RedisCacheCustomize(name, cacheWriter, cacheConfig != null ? cacheConfig : defaultCacheConfig);
        }
    }
    
    

    5)缓存配置类

    package com.spboot.config.cache;
    
    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))
                    .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(stringRedisSerializer))  // 设置 k v 序列化机制
                    .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer));
    
    
            //初始化RedisCacheManager
    //        RedisCacheManager cacheManager = new RedisCacheManager(redisCacheWriter, defaultCacheConfig);
           // 重点在这里,使用自己的CacheManager
            RedisCacheManager cacheManager = new RedisCacheManagerCustomize(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;
        }
    
    
    }
    
    

    6) 使用注解

    假设在一个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;
    	}
    }
    

    代码以及封装的starter

  • 相关阅读:
    LeetCode 566 重塑矩阵
    LeetCode 283 移动零
    C++Template(类模板二)
    Qt之简单绘图实现
    QT控件之QSlider
    Redis
    布局总结三: icon图标+标题上下两排排列
    vue中在data中引入图片的路径方法
    布局总结二:宽高比固定比例---移动端
    在vue中使用vue-awesome-swiper插件
  • 原文地址:https://www.cnblogs.com/bartggg/p/15037040.html
Copyright © 2020-2023  润新知