zoukankan      html  css  js  c++  java
  • Spring boot 拾遗 —— Spring Cache 扩展 Duration

    1 前言

    在上一篇我们改写了 CacheManager 使得它能够解析 cacheName#duration 动态设置 TTL,现在我们将使用预定义的 CacheResolver 来让我们的代码能有下边的表现形式:  

     第一个方法在注解上规定了 TTL 是 5 分钟, 第二个方法可以传入一个 duration 参数作为 TTL

    2 DurationDetectCacheManager 的实现

    作为 TTL 动态设置的基础,它的设计方式已在上一篇中涉及,因此我只贴出类的代码

     1 package cn.pancc.spring.security.config.cache;
     2 
     3 import lombok.extern.slf4j.Slf4j;
     4 import org.springframework.boot.autoconfigure.cache.CacheProperties;
     5 import org.springframework.data.redis.cache.RedisCache;
     6 import org.springframework.data.redis.cache.RedisCacheConfiguration;
     7 import org.springframework.data.redis.cache.RedisCacheManager;
     8 import org.springframework.data.redis.cache.RedisCacheWriter;
     9 import org.springframework.lang.Nullable;
    10 import org.springframework.util.StringUtils;
    11 
    12 import javax.annotation.Nonnull;
    13 import java.time.Duration;
    14 import java.time.format.DateTimeParseException;
    15 import java.util.Optional;
    16 
    17 /**
    18  * @author pancc
    19  */
    20 @Slf4j
    21 public class DurationDetectCacheManager extends RedisCacheManager {
    22     private static final String          TAG = "#";
    23     private final        CacheProperties cacheProperties;
    24 
    25     public DurationDetectCacheManager(RedisCacheWriter cacheWriter,
    26                                       RedisCacheConfiguration defaultCacheConfiguration,
    27                                       CacheProperties cacheProperties) {
    28         super(cacheWriter, defaultCacheConfiguration);
    29         this.cacheProperties = cacheProperties;
    30     }
    31 
    32     @Nonnull
    33     @Override
    34     protected RedisCache createRedisCache(@Nonnull String name, @Nullable RedisCacheConfiguration cacheConfig) {
    35         cacheConfig = cacheConfig == null ? RedisCacheConfiguration.defaultCacheConfig() : cacheConfig;
    36         String[] array = StringUtils.delimitedListToStringArray(name, TAG);
    37         name = array[0];
    38         if (array.length > 1) {
    39             try {
    40                 Duration duration = Duration.parse(array[1]);
    41                 cacheConfig = cacheConfig.entryTtl(duration);
    42             } catch (DateTimeParseException e) {
    43                 log.error("错误的 TTL 格式");
    44                 cacheConfig = cacheConfig.entryTtl(
    45                         Optional.ofNullable(cacheProperties.getRedis().getTimeToLive()).orElse(Duration.ofSeconds(0)));
    46             }
    47         }
    48         return super.createRedisCache(name, cacheConfig);
    49     }
    50 
    51 }

    3 DurationDetectCacheResolver 的实现

    3.1 规定

    让我们对方法参数做如下规定, Duration 参数可以为 0 到多个, 但是至少存在一个的情况下,只取第一个解析为适应我们的 CacheManager 实现的  Cache 

    3.2 思路

    CacheResolver#resolveCaches 方法中的参数 CacheOperationInvocationContext 我们可以获得当前调用方法上的所有 cacheNames ,可以获得方法入参,观察默认实现 AbstractCacheResolver 我们可以注意到最终进入获取 cache 是调用  CacheManager#getCache 的,实际上最终会走到我们的 CacheManager 的覆盖实现方法上

    3.3 代码实现

     1 package cn.pancc.spring.security.config.cache;
     2 
     3 import lombok.extern.slf4j.Slf4j;
     4 import org.springframework.cache.CacheManager;
     5 import org.springframework.cache.interceptor.AbstractCacheResolver;
     6 import org.springframework.cache.interceptor.BasicOperation;
     7 import org.springframework.cache.interceptor.CacheOperationInvocationContext;
     8 import org.springframework.util.StringUtils;
     9 
    10 import javax.annotation.Nonnull;
    11 import java.time.Duration;
    12 import java.util.Collection;
    13 import java.util.Set;
    14 import java.util.stream.Collectors;
    15 
    16 /**
    17  * @author pancc
    18  * @see org.springframework.cache.annotation.CachePut
    19  * @see org.springframework.cache.annotation.Cacheable
    20  * @see CacheConfig#cacheManager()
    21  */
    22 @Slf4j
    23 public class DurationDetectCacheResolver extends AbstractCacheResolver {
    24     private static final String TAG = "#";
    25 
    26     public DurationDetectCacheResolver(@Nonnull CacheManager cacheManager) {
    27         super(cacheManager);
    28     }
    29 
    30     @Override
    31     protected Collection<String> getCacheNames(@Nonnull CacheOperationInvocationContext<?> context) {
    32         final BasicOperation operation  = context.getOperation();
    33         final Object[]       args       = context.getArgs();
    34         Set<String>          cacheNames = operation.getCacheNames();
    35         if (args.length == 0) {
    36             return cacheNames;
    37         }
    38         for (final Object o : args) {
    39             if (Duration.class.isAssignableFrom(o.getClass())) {
    40                 final Duration duration = (Duration) o;
    41                 cacheNames = cacheNames.stream()
    42                         .map(this::removeTag)
    43                         .map(name -> this.appendDuration(name, duration))
    44                         .collect(Collectors.toSet());
    45             }
    46         }
    47         log.debug("resolve cacheNames [{}] for method {}",cacheNames, context.getMethod());
    48         return cacheNames;
    49     }
    50 
    51     @Nonnull
    52     private String appendDuration(@Nonnull String name, @Nonnull Duration duration) {
    53         return name + TAG + duration.toString();
    54     }
    55 
    56     @Nonnull
    57     private String removeTag(@Nonnull String name) {
    58         return StringUtils.delimitedListToStringArray(name, TAG)[0];
    59     }
    60 }

    3 合并配置与调用

    3.1 缓存的顶层配置

     1 package cn.pancc.spring.security.config.cache;
     2 
     3 import com.fasterxml.jackson.databind.ObjectMapper;
     4 import lombok.extern.slf4j.Slf4j;
     5 import org.springframework.boot.autoconfigure.cache.CacheProperties;
     6 import org.springframework.boot.context.properties.EnableConfigurationProperties;
     7 import org.springframework.cache.CacheManager;
     8 import org.springframework.cache.annotation.CachingConfigurerSupport;
     9 import org.springframework.cache.annotation.EnableCaching;
    10 import org.springframework.cache.interceptor.CacheResolver;
    11 import org.springframework.context.annotation.Bean;
    12 import org.springframework.context.annotation.Configuration;
    13 import org.springframework.data.redis.cache.RedisCacheConfiguration;
    14 import org.springframework.data.redis.cache.RedisCacheWriter;
    15 import org.springframework.data.redis.connection.RedisConnectionFactory;
    16 import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
    17 import org.springframework.data.redis.serializer.RedisSerializationContext;
    18 import org.springframework.data.redis.serializer.RedisSerializer;
    19 
    20 import javax.annotation.Nonnull;
    21 import java.time.Duration;
    22 import java.util.Optional;
    23 
    24 /**
    25  * @author pancc
    26  * @version 1.0
    27  */
    28 @EnableCaching
    29 @EnableConfigurationProperties(CacheProperties.class)
    30 @Slf4j
    31 @Configuration
    32 public class CacheConfig extends CachingConfigurerSupport {
    33 
    34     private final CacheProperties        cacheProperties;
    35     private final RedisConnectionFactory redisConnectionFactory;
    36     private final ObjectMapper           objectMapper;
    37 
    38     public CacheConfig(CacheProperties cacheProperties, RedisConnectionFactory redisConnectionFactory, ObjectMapper objectMapper) {
    39         this.cacheProperties        = cacheProperties;
    40         this.redisConnectionFactory = redisConnectionFactory;
    41         this.objectMapper           = objectMapper;
    42     }
    43 
    44     @Bean
    45     @Override
    46     public CacheManager cacheManager() {
    47         return new DurationDetectCacheManager(redisCacheWriter(redisConnectionFactory),
    48                 redisCacheConfiguration(),
    49                 cacheProperties);
    50     }
    51 
    52 
    53     @Bean
    54     public CacheResolver cacheResolver(@Nonnull CacheManager cacheManager) {
    55         return new DurationDetectCacheResolver(cacheManager);
    56     }
    57 
    58     public RedisCacheWriter redisCacheWriter(@Nonnull RedisConnectionFactory redisConnectionFactory) {
    59         return RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory);
    60     }
    61 
    62 
    63     public RedisCacheConfiguration redisCacheConfiguration() {
    64         RedisCacheConfiguration config          = RedisCacheConfiguration.defaultCacheConfig();
    65         CacheProperties.Redis   redisProperties = cacheProperties.getRedis();
    66         if (redisProperties.getTimeToLive() != null) {
    67             config = config.entryTtl(redisProperties.getTimeToLive());
    68         }
    69         if (redisProperties.getKeyPrefix() != null) {
    70             config = config.prefixKeysWith(redisProperties.getKeyPrefix());
    71         }
    72         if (!redisProperties.isCacheNullValues()) {
    73             config = config.disableCachingNullValues();
    74         }
    75         if (!redisProperties.isUseKeyPrefix()) {
    76             config = config.disableKeyPrefix();
    77         }
    78         config = config.entryTtl(Optional.ofNullable(redisProperties.getTimeToLive()).orElse(Duration.ofSeconds(0)));
    79         config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(RedisSerializer.string()));
    80         config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer(objectMapper)));
    81         return config;
    82     }
    83 }

    3.2 调用接口规定与实现

     1 package cn.pancc.spring.security.cache;
     2 
     3 import org.springframework.cache.annotation.CacheEvict;
     4 import org.springframework.cache.annotation.CachePut;
     5 import org.springframework.cache.annotation.Cacheable;
     6 
     7 import javax.annotation.Nonnull;
     8 import javax.annotation.Nullable;
     9 import java.time.Duration;
    10 
    11 /**
    12  * @author pancc
    13  */
    14 public interface Cache {
    15 
    16     /**
    17      * 从缓存中加载实体
    18      *
    19      * @param key 缓存 key
    20      * @return 缓存的实体, 或者 null 当不在缓存中时
    21      * @see Cacheable
    22      */
    23     @Nullable
    24     Object load(@Nonnull String key);
    25 
    26 
    27     /**
    28      * 缓存实体
    29      *
    30      * @param key 缓存的 key
    31      * @param o   缓存实体
    32      * @return 缓存实体
    33      * @see CachePut
    34      */
    35     @Nonnull
    36     Object cache(@Nonnull String key, @Nonnull Object o);
    37 
    38 
    39     /**
    40      * 缓存实体
    41      *
    42      * @param key      缓存的 key
    43      * @param o        缓存实体
    44      * @param duration 缓存的过期时间
    45      * @return 缓存实体
    46      * @see CachePut
    47      */
    48     @Nonnull
    49     Object cache(@Nonnull String key, @Nonnull Object o, @Nonnull Duration duration);
    50 
    51 
    52     /**
    53      * 清除 key 对应的缓存
    54      *
    55      * @param key key
    56      * @see CacheEvict
    57      */
    58     void evict(@Nonnull String key);
    59 
    60     /**
    61      * 清除 namespace 下所有的缓存
    62      *
    63      * @see CacheEvict
    64      */
    65     void flush();
    66 }
    Cache
     1 package cn.pancc.spring.security.cache;
     2 
     3 import lombok.extern.slf4j.Slf4j;
     4 import org.springframework.cache.annotation.CacheConfig;
     5 import org.springframework.cache.annotation.CacheEvict;
     6 import org.springframework.cache.annotation.CachePut;
     7 import org.springframework.cache.annotation.Cacheable;
     8 import org.springframework.stereotype.Component;
     9 
    10 import javax.annotation.Nonnull;
    11 import javax.annotation.Nullable;
    12 import java.time.Duration;
    13 
    14 /**
    15  * @author pancc
    16  */
    17 @Component
    18 @Slf4j
    19 @CacheConfig(cacheNames = "captcha",cacheResolver = "cacheResolver")
    20 public class CaptchaCache implements Cache {
    21     @CachePut(cacheNames = "captcha#PT5M",
    22             key = "#uuid", unless = "#result == null")
    23     @Override
    24     @Nonnull
    25     public Object cache(@Nonnull String uuid, @Nonnull Object code) {
    26         return code;
    27     }
    28 
    29     @CachePut(key = "#uuid", unless = "#result == null")
    30     @Nonnull
    31     @Override
    32     public Object cache(@Nonnull String uuid, @Nonnull Object code, @Nonnull Duration duration) {
    33         return code;
    34     }
    35 
    36     @Cacheable(
    37             key = "#uuid", unless = "#result==null")
    38     @Nullable
    39     @Override
    40     public Object load(@Nonnull String uuid) {
    41         return null;
    42     }
    43 
    44 
    45     @CacheEvict(key = "#key")
    46     @Override
    47     public void evict(@Nonnull String key) {
    48 
    49     }
    50 
    51     @CacheEvict(allEntries = true)
    52     @Override
    53     public void flush() {
    54 
    55     }
    56 }
    CaptchaCache

    3.3 测试类

     1 package cn.pancc.spring.security.cache;
     2 
     3 import cn.hutool.captcha.CaptchaUtil;
     4 import cn.hutool.captcha.LineCaptcha;
     5 import org.junit.jupiter.api.Test;
     6 import org.springframework.beans.factory.annotation.Autowired;
     7 import org.springframework.boot.test.context.SpringBootTest;
     8 
     9 import java.time.Duration;
    10 import java.util.UUID;
    11 
    12 @SpringBootTest
    13 class CaptchaCacheTest {
    14     @Autowired
    15     private CaptchaCache captchaCache;
    16 
    17     @Test
    18     void cache() {
    19         int count = 300;
    20         for (int i = 0; i < count; i++) {
    21             LineCaptcha captcha = CaptchaUtil.createLineCaptcha(120, 32, 5, 4);
    22             String      uuid    = UUID.randomUUID().toString();
    23             String      code    = captcha.getCode();
    24             captchaCache.cache(uuid, code, Duration.ofDays(1));
    25         }
    26     }
    27 }

    测试结果如期

  • 相关阅读:
    手写spring事务框架, 揭秘AOP实现原理。
    centos7修改端口登陆
    数据库的锁机制
    linux安装mysql5.6
    SpringMVC数据格式化
    Java处理小数点后几位
    docker学习(七)常见仓库介绍
    docker学习(六) Docker命令查询
    docker学习(六)
    docker学习(五)
  • 原文地址:https://www.cnblogs.com/siweipancc/p/13173056.html
Copyright © 2011-2022 走看看