SpringBoot两级缓存实现

 主要实现代码如下:

/**
 * 1. 支持解析 @CacheConfig.cacheNames ,独立为每个方法设置过期时间,单位秒
 * 2. 返回增强后的Cache实例 :new RedisPlusCache
 *
 * @see RedisPlusCache
 **/
public class RedisCachePlusManager extends RedisCacheManager {
    private final CachePlusConfig config;

    public RedisCachePlusManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration, CachePlusConfig config) {
        super(cacheWriter, defaultCacheConfiguration);
        this.config = config.clone();
    }

    @NonNull
    @Override
    protected RedisCache createRedisCache(@NonNull String name, @Nullable RedisCacheConfiguration cacheConfig) {
        String[] array = name.split(this.config.getDelimiter());
        name = array[0];
        if (array.length > 1 && cacheConfig != null) {
            long ttl = Long.parseLong(array[1]);
            cacheConfig = cacheConfig.entryTtl(Duration.ofSeconds(ttl));
        }
        if (array.length > 2) {
            this.config.setDuration(Long.parseLong(array[2]));
        }
        RedisCache redisCache = super.createRedisCache(name, cacheConfig);
        return new RedisPlusCache(redisCache.getName(), redisCache.getNativeCache(), redisCache.getCacheConfiguration(), this.config);
    }
}
/**
 * 1. 基于CaffeineCache实现二级缓存
 *
 * @see CachePlusConfig
 **/
public class RedisPlusCache extends RedisCache {
    private final Map<Long, Cache<Object, Object>> caffeineCaches;
    private final CachePlusConfig config;

    protected RedisPlusCache(String name, RedisCacheWriter cacheWriter, RedisCacheConfiguration cacheConfig, CachePlusConfig config) {
        super(name, cacheWriter, cacheConfig);
        this.caffeineCaches = new ConcurrentHashMap<>();
        this.config = config;
    }

    @Override
    protected Object lookup(@NonNull Object key) {
        RedisCacheConfiguration cacheConfiguration = super.getCacheConfiguration();
        Duration duration = cacheConfiguration.getTtl();
        Cache<Object, Object> localCache = this.caffeineCaches.get(duration.getSeconds());
        if (localCache == null) return this.lookupAndPutLocal(key);
        Object v = localCache.get(key, oneKey -> null);
        return v == null ? this.lookupAndPutLocal(key) : v;
    }

    @Override
    public void put(@NonNull Object key, @Nullable Object value) {
        this.putLocal(key, value);
        super.put(key, value);
    }

    private Object lookupAndPutLocal(@NonNull Object key) {
        Object v = super.lookup(key);
        this.putLocal(key, v);
        return v;
    }

    private void putLocal(Object key, Object value) {
        Object cacheValue = preProcessCacheValue(value);
        if (cacheValue != null) {
            Cache<Object, Object> localCache = this.createLocalCache();
            localCache.put(key, cacheValue);
        }
    }

    private Cache<Object, Object> createLocalCache() {
        long time = this.getCacheConfiguration().getTtl().getSeconds();
        Cache<Object, Object> localCache = this.caffeineCaches.get(time);
        if (localCache != null) return localCache;
        synchronized (this) {
            localCache = this.caffeineCaches.get(time);
            if (localCache != null) return localCache;
            localCache = Caffeine.newBuilder()
                    .initialCapacity((int) (this.config.getDuration() >> 2))
                    .expireAfterWrite(this.config.getDuration(), TimeUnit.SECONDS)
                    .maximumSize(this.config.getMaximumSize()).build();
            this.caffeineCaches.put(time, localCache);
            return localCache;
        }
    }
}
/**
 * 缓存配置类
 **/
@Data
public class CachePlusConfig implements Serializable, Cloneable {
    // 分隔符
    private String delimiter;
    // 过期时间
    private long duration;
    // 最大缓存容量
    private long maximumSize;

    public CachePlusConfig() {
        this.delimiter = "##";
        this.duration = 600L;
        this.maximumSize = 100_000L;
    }

    @Override
    public CachePlusConfig clone() {
        try {
            return (CachePlusConfig) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new IllegalStateException("clone error", e);
        }
    }
}

注册类:

@Configuration
@EnableCaching
public class SpringCacheConfig {

    @Resource
    @Qualifier("redisConnectionFactory")
    private RedisConnectionFactory redisConnectionFactory;

    @Resource
    private CachePlusConfig localCachePlusConfig;

    @ConfigurationProperties("cache-plus.local")
    @Bean
    public CachePlusConfig get() {
        return new CachePlusConfig();
    }

    @Bean("commonKeyGenerator")
    public KeyGenerator getCommonKeyGenerator() {
        return new CommonKeyGenerator();
    }

    @Primary
    @Bean
    public CacheManager cacheManager() {
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofSeconds(300));
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        GenericFastJsonRedisSerializer genericFastJsonRedisSerializer = new GenericFastJsonRedisSerializer();
        redisCacheConfiguration = redisCacheConfiguration.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(stringRedisSerializer))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(genericFastJsonRedisSerializer));
        return new RedisCachePlusManager(RedisCacheWriter.lockingRedisCacheWriter(redisConnectionFactory), redisCacheConfiguration, localCachePlusConfig);
    }

    @Bean("localCacheManager")
    public CacheManager localCacheManager() {
        CaffeineCacheManager localCacheManager = new CaffeineCachePlusManager(this.localCachePlusConfig);
        localCacheManager.setAllowNullValues(true);
        return localCacheManager;
    }
}
@Configuration
public class RedisCommonConfig {

    @ConditionalOnMissingBean(name = "redisTemplate")
    @Bean(name = "redisTemplate")
    public RedisTemplate<String, Object> redisTemplate(@Qualifier("redisConnectionFactory") RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        GenericFastJsonRedisSerializer genericFastJsonRedisSerializer = new GenericFastJsonRedisSerializer();
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        // 设置value的序列化规则和 key的序列化规则
        redisTemplate.setKeySerializer(stringRedisSerializer);
        redisTemplate.setValueSerializer(genericFastJsonRedisSerializer);
        redisTemplate.setHashKeySerializer(stringRedisSerializer);
        redisTemplate.setHashValueSerializer(genericFastJsonRedisSerializer);
        redisTemplate.setDefaultSerializer(genericFastJsonRedisSerializer);
        redisTemplate.setEnableDefaultSerializer(true);
        return redisTemplate;
    }

    @ConditionalOnMissingBean(name = "redisConnectionFactory")
    @Bean(name = "redisConnectionFactory")
    public RedisConnectionFactory redisConnectionFactory() {
        RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration("127.0.0.1", 6379);
        return new LettuceConnectionFactory(configuration);
    }
}

扩展,单独注册只支持二级缓存(本地缓存)的CacheManager,基于CaffeineCache实现:

/**
 * 1. 支持解析 @CacheConfig.cacheNames ,独立为每个方法设置过期时间,单位秒
 **/
@Slf4j
public class CaffeineCachePlusManager extends CaffeineCacheManager {

    private final CachePlusConfig config;

    public CaffeineCachePlusManager(CachePlusConfig config) {
        this.config = config;
    }

    @NonNull
    @Override
    protected com.github.benmanes.caffeine.cache.Cache<Object, Object> createNativeCaffeineCache(@NonNull String name) {
        long expire = this.getExpire(name);
        if (expire > 0) {
            return Caffeine.newBuilder()
                    .initialCapacity((int)(this.config.getMaximumSize() >> 4))
                    // 3600秒后自动删除
                    .expireAfterWrite(expire, TimeUnit.SECONDS)
                    // 最大容量10w个,超过会自动清理空间
                    .maximumSize(this.config.getMaximumSize())
                    .removalListener(((key, value, cause) -> {
                        //清理通知 key, value ==> 键值对   cause ==> 清理原因
                        log.info("caffeine cache removed: {}, {}, {}", key, value, cause);
                    })).build();
        }
        return super.createNativeCaffeineCache(name);
    }

    private long getExpire(@NonNull String name) {
        if (this.config.getDelimiter() == null) return this.config.getDuration();
        String[] split = name.split(this.config.getDelimiter());
        return split.length > 1 ? Long.parseLong(split[1]) : this.config.getDuration();
    }
}

扩展,键生成器

/**
 * 通用的缓存键生成器
 **/
public class CommonKeyGenerator implements KeyGenerator {

    @NonNull
    @Override
    public Object generate(Object target, Method method, @NonNull Object... params) {
        return target.getClass().getName() + ":" + method.getName() + ":" + generateKey(params);
    }

    /**
     * Generate a key based on the specified parameters.
     */
    public static Object generateKey(Object... params) {
        if (params.length == 0) {
            return SimpleKey.EMPTY;
        }
        if (params.length == 1) {
            Object param = params[0];
            if (param != null && !param.getClass().isArray()) {
                return param;
            }
        }
        return StringUtils.arrayToDelimitedString(params, "-");
    }
}
/**
 * 缓存键生成器-从方法开始拼接
 **/
public class MethodKeyGenerator implements KeyGenerator {

    @NonNull
    @Override
    public Object generate(@NonNull Object target, Method method, @NonNull Object... params) {
        return method.getName() + ":" + CommonKeyGenerator.generateKey(params);
    }
}

业务代码示例:

@CacheConfig(cacheNames = "DICT##120", keyGenerator = "commonKeyGenerator")
@Service
public class RegionService {

    // 过期时间、键生成,继承@CacheConfig
    @Cacheable
    public String getCityName(String code) {
        if ("010".equals(code)) return "beijing";
        else if (("020".equals(code))) return "shanghai";
        else return "shenzhen";
    }

    @Cacheable
    public String getCityName(String code, String lang) {
        if ("010".equals(code)) return "beijing";
        else if (("020".equals(code))) return "shanghai";
        else return "shenzhen";
    }

    // 该方法独立设置过期时间240秒
    @Cacheable(cacheNames = "DICT##240")
    public String getCityName240(String code, String lang) {
        if ("010".equals(code)) return "beijing";
        else if (("020".equals(code))) return "shanghai";
        else return "shenzhen";
    }

    // 该方法只支持二级缓存
    @Cacheable(cacheNames = "DICT##10", cacheManager = "localCacheManager")
    public String getCityNameByLocal(String code) {
        if ("010".equals(code)) return "beijing";
        else if (("020".equals(code))) return "shanghai";
        else return "shenzhen";
    }

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值