Idea Java

SpringCache支持模糊匹配

Posted on 2021-01-24,8 min read
  • 在开发中如果是使用SpringCache来控制缓存的话,经常会遇到需要清除缓存的情况。我们希望其清除缓存时能支持模糊匹配,但是Spring自带的CacheManage是不支持模糊匹配的。

  • 使用Redis为例。常用的缓存注解有:

    • @CachePut
    • @Cacheable
    • @CacheEvict
  • 其中@CacheEvict是用于清除缓存的。但是其并不支持模糊匹配。这几个注解的功能是由类RedisCache提供的。而清除缓存的功能是由evict方法提供的。

  • @Override
    public void evict(Object key) {
        cacheWriter.remove(name, createAndConvertCacheKey(key));
    }
    
  • 很明显这个并不支持模糊匹配。

  • 这时我们注意到了其中的clear方法

  • @Override
    public void clear() {
    
        byte[] pattern = conversionService.convert(createCacheKey("*"), byte[].class);
        cacheWriter.clean(name, pattern);
    }
    
  • clear方法清除缓存的方法是通过模糊匹配清除的。这证明SpringCache是支持模糊匹配清除缓存的。而想调用clear方法需要使用注解@CacheEvict(allEntries = true)

  • 我们可以通过重写evict方法来使得清除缓存支持模糊匹配。

代码

重写RedisCache

/**
     * 重写RedisCache使其支持模糊匹配
     */
private static final class CustomizedRedisCache extends RedisCache{
    private final String name;
    private final RedisCacheWriter cacheWriter;
    private final ConversionService conversionService;
    protected CustomizedRedisCache(String name, RedisCacheWriter cacheWriter, RedisCacheConfiguration cacheConfig) {
        super(name, cacheWriter, cacheConfig);
        this.name = name;
        this.cacheWriter = cacheWriter;
        this.conversionService = cacheConfig.getConversionService();
    }

    @Override
    public void evict(Object key) {
        byte[] pattern = this.conversionService.convert(this.createCacheKey(key), byte[].class);
        this.cacheWriter.clean(this.name, pattern);
    }
}

重写RedisCacheManager

  • 由于RedisCacheManage中默认创建的是RedisCache

  • protected RedisCache createRedisCache(String name, @Nullable RedisCacheConfiguration cacheConfig) {
        return new RedisCache(name, cacheWriter, cacheConfig != null ? cacheConfig : defaultCacheConfig);
    }
    
  • 所以我们需要重写RedisCacheManager来注入我们自己的RedisCache

  • 在创建CacheManager的时候记得换成我们自己定义的CacheManager

/**
     * 重写RedisCacheManager,使得CustomizedRedisCache能够替换RedisCache
     */
private static final class CustomizedRedisCacheManager extends RedisCacheManager{

    private final RedisCacheWriter cacheWriter;
    private final RedisCacheConfiguration defaultCacheConfig;
    private final Map<String, RedisCacheConfiguration> initialCaches = new LinkedHashMap<>();
    private boolean enableTransactions;


    public CustomizedRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) {
        super(cacheWriter, defaultCacheConfiguration);
        this.cacheWriter = cacheWriter;
        this.defaultCacheConfig = defaultCacheConfiguration;
    }

    public CustomizedRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration, String... initialCacheNames) {
        super(cacheWriter, defaultCacheConfiguration, initialCacheNames);
        this.cacheWriter = cacheWriter;
        this.defaultCacheConfig = defaultCacheConfiguration;
    }

    public CustomizedRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration, boolean allowInFlightCacheCreation, String... initialCacheNames) {
        super(cacheWriter, defaultCacheConfiguration, allowInFlightCacheCreation, initialCacheNames);
        this.cacheWriter = cacheWriter;
        this.defaultCacheConfig = defaultCacheConfiguration;
    }

    public CustomizedRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration, Map<String, RedisCacheConfiguration> initialCacheConfigurations) {
        super(cacheWriter, defaultCacheConfiguration, initialCacheConfigurations);
        this.cacheWriter = cacheWriter;
        this.defaultCacheConfig = defaultCacheConfiguration;
    }

    public CustomizedRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration, Map<String, RedisCacheConfiguration> initialCacheConfigurations, boolean allowInFlightCacheCreation) {
        super(cacheWriter, defaultCacheConfiguration, initialCacheConfigurations, allowInFlightCacheCreation);
        this.cacheWriter = cacheWriter;
        this.defaultCacheConfig = defaultCacheConfiguration;
    }

    /**
         * 这个构造方法最重要
         **/
    public CustomizedRedisCacheManager(RedisConnectionFactory redisConnectionFactory, RedisCacheConfiguration cacheConfiguration) {
        this(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory),cacheConfiguration);
    }

    //覆盖父类创建RedisCache
    @Override
    protected RedisCache createRedisCache(String name, @Nullable RedisCacheConfiguration cacheConfig) {
        return new CustomizedRedisCache(name, cacheWriter, cacheConfig != null ? cacheConfig : defaultCacheConfig);
    }

    @Override
    public Map<String, RedisCacheConfiguration> getCacheConfigurations() {
        Map<String, RedisCacheConfiguration> configurationMap = new HashMap<>(getCacheNames().size());
        getCacheNames().forEach(it -> {
            RedisCache cache = CustomizedRedisCache.class.cast(lookupCache(it));
            configurationMap.put(it, cache != null ? cache.getCacheConfiguration() : null);
        });
        return Collections.unmodifiableMap(configurationMap);
    }
}

完整代码

package cn.gloduck.onlinetest.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.interceptor.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.DependsOn;
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.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.*;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.lang.Nullable;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.*;

public class CacheConfig extends CachingConfigurerSupport {
    @Autowired
    private RedisConnectionFactory factory;
    @Autowired
    private CacheManager cacheManager;

    /**
     * 初始化redis
     *
     * @param redisConnectionFactory
     * @return
     */
    @Bean(name = "redisTemplate")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();

        redisTemplate.setConnectionFactory(redisConnectionFactory);
        redisTemplate.setKeySerializer(keySerializer());
        redisTemplate.setHashKeySerializer(keySerializer());
        redisTemplate.setValueSerializer(valueSerializer());
        redisTemplate.setHashValueSerializer(valueSerializer());
        return redisTemplate;
    }


    @Bean
    @Override
    public CacheErrorHandler errorHandler() {
        // 用于捕获从Cache中进行CRUD时的异常的回调处理器。
        return new SimpleCacheErrorHandler();
    }


    @Bean
    @Override
    public CacheManager cacheManager() {
        RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.
                defaultCacheConfig()
                .disableCachingNullValues()
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(valueSerializer()));
        return new CustomizedRedisCacheManager(factory, cacheConfiguration);
    }

    @Bean
    @Override
    @DependsOn("cacheManager")
    public CacheResolver cacheResolver() {
        return new SimpleCacheResolver(cacheManager);
    }

    @Bean("customizedKeyGenerator")
    public CustomizedKeyGenerator myKeyGenerator(){
        return new CustomizedKeyGenerator();
    }

    /**
     * Redis键序列化器
     * @return
     */
    private RedisSerializer keySerializer(){
        return new StringRedisSerializer();
    }

    /**
     * Redis值序列化器
     * @return
     */
    private RedisSerializer valueSerializer(){
        return new Jackson2JsonRedisSerializer<Serializable>(Serializable.class);
    }

    /**
     * 自定义Redis的KeyGenerator,取代自带的SimpleKeyGenerator
     */
    private static final class CustomizedKeyGenerator implements KeyGenerator{
        @Override
        public Object generate(Object target, Method method, Object... params) {
            if(params.length == 0){
                return "defaultKey";
            }
            int length = params.length - 1;
            StringBuilder keyBuilder = new StringBuilder();
            for (int i = 0; i < length; i++) {
                keyBuilder.append(params[i]).append("-");
            }
            keyBuilder.append(params[length]);
            return keyBuilder.toString();
        }

    }

    /**
     * 重写RedisCache使其支持模糊匹配
     */
    private static final class CustomizedRedisCache extends RedisCache{
        private final String name;
        private final RedisCacheWriter cacheWriter;
        private final ConversionService conversionService;
        protected CustomizedRedisCache(String name, RedisCacheWriter cacheWriter, RedisCacheConfiguration cacheConfig) {
            super(name, cacheWriter, cacheConfig);
            this.name = name;
            this.cacheWriter = cacheWriter;
            this.conversionService = cacheConfig.getConversionService();
        }

        @Override
        public void evict(Object key) {
            byte[] pattern = this.conversionService.convert(this.createCacheKey(key), byte[].class);
            this.cacheWriter.clean(this.name, pattern);
        }
    }
    /**
     * 重写RedisCacheManager,使得CustomizedRedisCache能够替换RedisCache
     */
    private static final class CustomizedRedisCacheManager extends RedisCacheManager{

        private final RedisCacheWriter cacheWriter;
        private final RedisCacheConfiguration defaultCacheConfig;
        private final Map<String, RedisCacheConfiguration> initialCaches = new LinkedHashMap<>();
        private boolean enableTransactions;


        public CustomizedRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) {
            super(cacheWriter, defaultCacheConfiguration);
            this.cacheWriter = cacheWriter;
            this.defaultCacheConfig = defaultCacheConfiguration;
        }

        public CustomizedRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration, String... initialCacheNames) {
            super(cacheWriter, defaultCacheConfiguration, initialCacheNames);
            this.cacheWriter = cacheWriter;
            this.defaultCacheConfig = defaultCacheConfiguration;
        }

        public CustomizedRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration, boolean allowInFlightCacheCreation, String... initialCacheNames) {
            super(cacheWriter, defaultCacheConfiguration, allowInFlightCacheCreation, initialCacheNames);
            this.cacheWriter = cacheWriter;
            this.defaultCacheConfig = defaultCacheConfiguration;
        }

        public CustomizedRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration, Map<String, RedisCacheConfiguration> initialCacheConfigurations) {
            super(cacheWriter, defaultCacheConfiguration, initialCacheConfigurations);
            this.cacheWriter = cacheWriter;
            this.defaultCacheConfig = defaultCacheConfiguration;
        }

        public CustomizedRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration, Map<String, RedisCacheConfiguration> initialCacheConfigurations, boolean allowInFlightCacheCreation) {
            super(cacheWriter, defaultCacheConfiguration, initialCacheConfigurations, allowInFlightCacheCreation);
            this.cacheWriter = cacheWriter;
            this.defaultCacheConfig = defaultCacheConfiguration;
        }

        /**
         * 这个构造方法最重要
         **/
        public CustomizedRedisCacheManager(RedisConnectionFactory redisConnectionFactory, RedisCacheConfiguration cacheConfiguration) {
            this(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory),cacheConfiguration);
        }

        //覆盖父类创建RedisCache
        @Override
        protected RedisCache createRedisCache(String name, @Nullable RedisCacheConfiguration cacheConfig) {
            return new CustomizedRedisCache(name, cacheWriter, cacheConfig != null ? cacheConfig : defaultCacheConfig);
        }

        @Override
        public Map<String, RedisCacheConfiguration> getCacheConfigurations() {
            Map<String, RedisCacheConfiguration> configurationMap = new HashMap<>(getCacheNames().size());
            getCacheNames().forEach(it -> {
                RedisCache cache = CustomizedRedisCache.class.cast(lookupCache(it));
                configurationMap.put(it, cache != null ? cache.getCacheConfiguration() : null);
            });
            return Collections.unmodifiableMap(configurationMap);
        }
    }
}


下一篇: SpringBoot Xss过滤→

loading...