springmvc redis @Cacheable扩展(一)

springmvc 中有自带的cache处理模块,可以是方法级别的缓存处理,那么在实际使用中,很可能自己造轮子,因为实际中永远会有更奇怪的需求点。比如:
1 清除缓存时候,能模糊的进行删除
2 针对不同的key,设置不同的过期时间
这2个是有些麻烦的需求,当然针对缓存内容,设置 key(这个 key 的确定)更让人难受,不好取舍,需要有一定的开发经验,否则只能不停的修改。
我们先集中处理第一个问题,模糊删除

  • 查找方案
  • 查看低版本redis实现
  • 具体处理方式

明确问题,查找方案

可能网上有不少的解决方案

  1. 直接重写 //blog.csdn.net/Crystalqy/article/details/110681684
  2. spring 5 + 版本的 //my.oschina.net/u/220938/blog/3196609
  3. 具有启发性的 //blog.csdn.net/yali_aini/article/details/89923548

1. 首先我们从网上找到对应的修改的code,真的就是拿来就能用的那种,然后发现有2个function没有,然后就发现你是低版本,然后就没然后了。。

    <properties>
        <org.springframework-version>4.2.2.RELEASE</org.springframework-version>
        <org.aspectj-version>1.8.2</org.aspectj-version>
        <org.slf4j-version>1.7.21</org.slf4j-version>
        <org.log4j2-version>2.8.2</org.log4j2-version>
    </properties>

2. 根据第三个,可以看到,基于 redis template 的缓存处理,是有模糊处理的方法的,也就是说,可以做模糊处理。

3. 查看 spring 低版本 4.2.2 版本的 cache 的redis 类,进行简单的 仿做


查看低版本redis实现

因为使用springmvc时候,都会对 redis 进行配置,设置 ttl 等参数,那么,点进去看源码,就会发现
CustomizedRedisCacheManagerCustomizeRedisCache ,和 高版本的名字很像,那么仔细看看,发现 CustomizeRedisCache 就是需要改造的。

  public void evict(RedisCacheElement element)
  public void evict(Object key) 

这2个函数。很可以,2个文件粘贴出来,直接做成注入,发现就直接可以在 @Cacheable 的时候断点看了。
这2个就是在删除缓存时候使用的。


改造一波

CustomizedRedisCacheManager

 package oldmvc.config.cache;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cache.Cache;
import org.springframework.cache.transaction.TransactionAwareCacheDecorator;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.cache.DefaultRedisCachePrefix;
import org.springframework.data.redis.cache.RedisCache;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCachePrefix;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * CustomizedRedisCacheManager
 *
 * @desc  重新定义 oldcache 的 处理方式
 */

public class CustomizedRedisCacheManager extends RedisCacheManager {
    private final Log logger;
    private final RedisOperations redisOperations;
    private boolean usePrefix;
    private RedisCachePrefix cachePrefix;
    private boolean loadRemoteCachesOnStartup;
    private boolean dynamic;
    private long defaultExpiration;
    private Map<String, Long> expires;
    private Set<String> configuredCacheNames;

    public CustomizedRedisCacheManager(RedisOperations redisOperations) {
        this(redisOperations, Collections.emptyList());
    }

    public CustomizedRedisCacheManager(RedisOperations redisOperations, Collection<String> cacheNames) {
        super(redisOperations, cacheNames);

        this.logger = LogFactory.getLog(CustomizedRedisCacheManager.class);
        this.usePrefix = false;
        this.cachePrefix = new DefaultRedisCachePrefix();
        this.loadRemoteCachesOnStartup = false;
        this.dynamic = true;
        this.defaultExpiration = 0L;
        this.expires = null;
        this.redisOperations = redisOperations;
        this.setCacheNames(cacheNames);
    }

    public Cache getCache(String name) {
        Cache cache = super.getCache(name);
        return cache == null && this.dynamic ? this.createAndAddCache(name) : cache;
    }

    public void setCacheNames(Collection<String> cacheNames) {
        Set<String> newCacheNames = CollectionUtils.isEmpty(cacheNames) ? Collections.emptySet() : new HashSet(cacheNames);
        this.configuredCacheNames = (Set) newCacheNames;
        this.dynamic = ((Set) newCacheNames).isEmpty();
    }

    public void setUsePrefix(boolean usePrefix) {
        this.usePrefix = usePrefix;
    }

    public void setCachePrefix(RedisCachePrefix cachePrefix) {
        this.cachePrefix = cachePrefix;
    }

    public void setDefaultExpiration(long defaultExpireTime) {
        this.defaultExpiration = defaultExpireTime;
    }

    public void setExpires(Map<String, Long> expires) {
        this.expires = expires != null ? new ConcurrentHashMap(expires) : null;
    }

    public void setLoadRemoteCachesOnStartup(boolean loadRemoteCachesOnStartup) {
        this.loadRemoteCachesOnStartup = loadRemoteCachesOnStartup;
    }

    protected Collection<? extends Cache> loadCaches() {
        Assert.notNull(this.redisOperations, "A redis template is required in order to interact with data store");
        return this.addConfiguredCachesIfNecessary(this.loadRemoteCachesOnStartup ? this.loadAndInitRemoteCaches() : Collections.emptyList());
    }

    protected Collection<? extends Cache> addConfiguredCachesIfNecessary(Collection<? extends Cache> caches) {
        Assert.notNull(caches, "Caches must not be null!");
        Collection<Cache> result = new ArrayList(caches);
        Iterator var3 = this.getCacheNames().iterator();

        while (var3.hasNext()) {
            String cacheName = (String) var3.next();
            boolean configuredCacheAlreadyPresent = false;
            Iterator var6 = caches.iterator();

            while (var6.hasNext()) {
                Cache cache = (Cache) var6.next();
                if (cache.getName().equals(cacheName)) {
                    configuredCacheAlreadyPresent = true;
                    break;
                }
            }

            if (!configuredCacheAlreadyPresent) {
                result.add(this.getCache(cacheName));
            }
        }

        return result;
    }

    protected Cache createAndAddCache(String cacheName) {
        this.addCache(this.createCache(cacheName));
        return super.getCache(cacheName);
    }

    protected RedisCache createCache(String cacheName) {
        long expiration = this.computeExpiration(cacheName);

        return new CustomizeRedisCache(cacheName, this.usePrefix ? this.cachePrefix.prefix(cacheName) : null, this.redisOperations, expiration);

//        return new RedisCache(cacheName, this.usePrefix ? this.cachePrefix.prefix(cacheName) : null, this.redisOperations, expiration);
    }

    protected long computeExpiration(String name) {
        Long expiration = null;
        if (this.expires != null) {
            expiration = (Long) this.expires.get(name);
        }

        return expiration != null ? expiration.longValue() : this.defaultExpiration;
    }

    protected List<Cache> loadAndInitRemoteCaches() {
        ArrayList caches = new ArrayList();

        try {
            Set<String> cacheNames = this.loadRemoteCacheKeys();
            if (!CollectionUtils.isEmpty(cacheNames)) {
                Iterator var3 = cacheNames.iterator();

                while (var3.hasNext()) {
                    String cacheName = (String) var3.next();
                    if (null == super.getCache(cacheName)) {
                        caches.add(this.createCache(cacheName));
                    }
                }
            }
        } catch (Exception var5) {
            if (this.logger.isWarnEnabled()) {
                this.logger.warn("Failed to initialize cache with remote cache keys.", var5);
            }
        }

        return caches;
    }

    protected Set<String> loadRemoteCacheKeys() {
        return (Set) this.redisOperations.execute(new RedisCallback<Set<String>>() {
            public Set<String> doInRedis(RedisConnection connection) throws DataAccessException {
                Set<byte[]> keys = connection.keys(CustomizedRedisCacheManager.this.redisOperations.getKeySerializer().serialize("*~keys"));
                Set<String> cacheKeys = new LinkedHashSet();
                if (!CollectionUtils.isEmpty(keys)) {
                    Iterator var4 = keys.iterator();

                    while (var4.hasNext()) {
                        byte[] key = (byte[]) var4.next();
                        cacheKeys.add(CustomizedRedisCacheManager.this.redisOperations.getKeySerializer().deserialize(key).toString().replace("~keys", ""));
                    }
                }

                return cacheKeys;
            }
        });
    }

    protected RedisOperations getRedisOperations() {
        return this.redisOperations;
    }

    protected RedisCachePrefix getCachePrefix() {
        return this.cachePrefix;
    }

    protected boolean isUsePrefix() {
        return this.usePrefix;
    }

    public void afterPropertiesSet() {
        if (!CollectionUtils.isEmpty(this.configuredCacheNames)) {
            Iterator var1 = this.configuredCacheNames.iterator();

            while (var1.hasNext()) {
                String cacheName = (String) var1.next();
                this.createAndAddCache(cacheName);
            }

            this.configuredCacheNames.clear();
        }

        super.afterPropertiesSet();
    }

    protected Cache decorateCache(Cache cache) {
        return this.isCacheAlreadyDecorated(cache) ? cache : super.decorateCache(cache);
    }

    protected boolean isCacheAlreadyDecorated(Cache cache) {
        return this.isTransactionAware() && cache instanceof TransactionAwareCacheDecorator;
    }
}

CustomizeRedisCache


package oldmvc.config.cache;
import org.apache.commons.lang.StringUtils;
import org.springframework.cache.support.SimpleValueWrapper;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.RedisSystemException;
import org.springframework.data.redis.cache.RedisCache;
import org.springframework.data.redis.cache.RedisCacheElement;
import org.springframework.data.redis.cache.RedisCacheKey;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;

import java.lang.reflect.Constructor;
import java.util.Arrays;
import java.util.Set;
import java.util.concurrent.Callable;

/**
 * RedisCacheResolver
 *
 * @desc springCache 的重载
 */
public class CustomizeRedisCache extends RedisCache {
    private final RedisOperations redisOperations;
    private final CustomizeRedisCache.RedisCacheMetadata cacheMetadata;
    private final CacheValueAccessor cacheValueAccessor;

    public CustomizeRedisCache(String name, byte[] prefix, RedisOperations<? extends Object, ? extends Object> redisOperations, long expiration) {
        super(name, prefix, redisOperations, expiration);

        Assert.hasText(name, "non-empty cache name is required");
        this.cacheMetadata = new CustomizeRedisCache.RedisCacheMetadata(name, prefix);
        this.cacheMetadata.setDefaultExpiration(expiration);
        this.redisOperations = redisOperations;
        this.cacheValueAccessor = new CustomizeRedisCache.CacheValueAccessor(redisOperations.getValueSerializer());
    }

    public <T> T get(Object key, Class<T> type) {
        ValueWrapper wrapper = this.get(key);
        return wrapper == null ? null : (T) wrapper.get();
    }

    public ValueWrapper get(Object key) {
        return this.get((new RedisCacheKey(key)).usePrefix(this.cacheMetadata.getKeyPrefix()).withKeySerializer(this.redisOperations.getKeySerializer()));
    }

    public <T> T get(Object key, Callable<T> valueLoader) {
        CustomizeRedisCache.BinaryRedisCacheElement rce = new CustomizeRedisCache.BinaryRedisCacheElement(new RedisCacheElement((new RedisCacheKey(key)).usePrefix(this.cacheMetadata.getKeyPrefix()).withKeySerializer(this.redisOperations.getKeySerializer()), valueLoader), this.cacheValueAccessor);
        ValueWrapper val = this.get(key);
        if (val != null) {
            return (T) val.get();
        } else {
            CustomizeRedisCache.RedisWriteThroughCallback callback = new CustomizeRedisCache.RedisWriteThroughCallback(rce, this.cacheMetadata);

            try {
                byte[] result = (byte[]) ((byte[]) this.redisOperations.execute(callback));
                return result == null ? null : (T) this.cacheValueAccessor.deserializeIfNecessary(result);
            } catch (RuntimeException var7) {
                throw CustomizeRedisCache.CacheValueRetrievalExceptionFactory.INSTANCE.create(key, valueLoader, var7);
            }
        }
    }

    public RedisCacheElement get(RedisCacheKey cacheKey) {
        Assert.notNull(cacheKey, "CacheKey must not be null!");
        byte[] bytes = (byte[]) ((byte[]) this.redisOperations.execute(new CustomizeRedisCache.AbstractRedisCacheCallback<byte[]>(new CustomizeRedisCache.BinaryRedisCacheElement(new RedisCacheElement(cacheKey, (Object) null), this.cacheValueAccessor), this.cacheMetadata) {
            public byte[] doInRedis(CustomizeRedisCache.BinaryRedisCacheElement element, RedisConnection connection) throws DataAccessException {
                return connection.get(element.getKeyBytes());
            }
        }));
        return bytes == null ? null : new RedisCacheElement(cacheKey, this.cacheValueAccessor.deserializeIfNecessary(bytes));
    }

    public void put(Object key, Object value) {
        this.put((new RedisCacheElement((new RedisCacheKey(key)).usePrefix(this.cacheMetadata.getKeyPrefix()).withKeySerializer(this.redisOperations.getKeySerializer()), value)).expireAfter(this.cacheMetadata.getDefaultExpiration()));
    }

    public void put(RedisCacheElement element) {
        Assert.notNull(element, "Element must not be null!");
        this.redisOperations.execute(new CustomizeRedisCache.RedisCachePutCallback(new CustomizeRedisCache.BinaryRedisCacheElement(element, this.cacheValueAccessor), this.cacheMetadata));
    }

    public ValueWrapper putIfAbsent(Object key, Object value) {
        return this.putIfAbsent((new RedisCacheElement((new RedisCacheKey(key)).usePrefix(this.cacheMetadata.getKeyPrefix()).withKeySerializer(this.redisOperations.getKeySerializer()), value)).expireAfter(this.cacheMetadata.getDefaultExpiration()));
    }

    public ValueWrapper putIfAbsent(RedisCacheElement element) {
        Assert.notNull(element, "Element must not be null!");
        new CustomizeRedisCache.RedisCachePutIfAbsentCallback(new CustomizeRedisCache.BinaryRedisCacheElement(element, this.cacheValueAccessor), this.cacheMetadata);
        return this.toWrapper(this.cacheValueAccessor.deserializeIfNecessary((byte[]) ((byte[]) this.redisOperations.execute(new CustomizeRedisCache.RedisCachePutIfAbsentCallback(new CustomizeRedisCache.BinaryRedisCacheElement(element, this.cacheValueAccessor), this.cacheMetadata)))));
    }

    /**
     *  重点处理,进行重写
     *
     * @param key
     */
    public void evict(Object key) {
        if(key instanceof  String){
            String keyString=key.toString();
            if(StringUtils.endsWith(keyString,"*")){
//                evictLikePrefix(this.cacheMetadata.cacheName + keyString);
                evictLikePrefix(keyString);
                return;
            }
            if(StringUtils.startsWith(keyString,"*")){
//                evictLikePrefix(this.cacheMetadata.cacheName + keyString);
                evictLikePrefix(keyString);
                return;
            }
        }

        // 原始
        RedisCacheElement redisCacheElement = new RedisCacheElement((new RedisCacheKey(key)).usePrefix(this.cacheMetadata.getKeyPrefix()).withKeySerializer(this.redisOperations.getKeySerializer()), (Object) null);
        this.evict(redisCacheElement);



    }


    public void evict(RedisCacheElement element) {
        Assert.notNull(element, "Element must not be null!");
        this.redisOperations.execute(new CustomizeRedisCache.RedisCacheEvictCallback(new CustomizeRedisCache.BinaryRedisCacheElement(element, this.cacheValueAccessor), this.cacheMetadata));
    }


    /**
     * 进行模糊处理 key
     *
     * @param key
     */
    public void evictLikePrefix(Object key){
        Set keys = this.redisOperations.keys(key);
        if(keys != null && keys.size() > 0){
            for(Object k : keys){
                RedisCacheElement redisCacheElement = new RedisCacheElement((new RedisCacheKey(k)).usePrefix(this.cacheMetadata.getKeyPrefix()).withKeySerializer(this.redisOperations.getKeySerializer()), (Object) null);
                this.evict(redisCacheElement);
            }
        }
    }

    public void clear() {
        this.redisOperations.execute((RedisCallback) (this.cacheMetadata.usesKeyPrefix() ? new CustomizeRedisCache.RedisCacheCleanByPrefixCallback(this.cacheMetadata) : new CustomizeRedisCache.RedisCacheCleanByKeysCallback(this.cacheMetadata)));
    }

    public String getName() {
        return this.cacheMetadata.getCacheName();
    }

    public Object getNativeCache() {
        return this.redisOperations;
    }

    private ValueWrapper toWrapper(Object value) {
        return value != null ? new SimpleValueWrapper(value) : null;
    }


    static class RedisCacheMetadata {
        private final String cacheName;
        private final byte[] keyPrefix;
        private final byte[] setOfKnownKeys;
        private final byte[] cacheLockName;
        private long defaultExpiration = 0L;

        public RedisCacheMetadata(String cacheName, byte[] keyPrefix) {
            Assert.hasText(cacheName, "CacheName must not be null or empty!");
            this.cacheName = cacheName;
            this.keyPrefix = keyPrefix;
            StringRedisSerializer stringSerializer = new StringRedisSerializer();
            this.setOfKnownKeys = this.usesKeyPrefix() ? new byte[0] : stringSerializer.serialize(cacheName + "~keys");
            this.cacheLockName = stringSerializer.serialize(cacheName + "~lock");
        }

        public boolean usesKeyPrefix() {
            return this.keyPrefix != null && this.keyPrefix.length > 0;
        }

        public byte[] getKeyPrefix() {
            return this.keyPrefix;
        }

        public byte[] getSetOfKnownKeysKey() {
            return this.setOfKnownKeys;
        }

        public byte[] getCacheLockKey() {
            return this.cacheLockName;
        }

        public String getCacheName() {
            return this.cacheName;
        }

        public void setDefaultExpiration(long seconds) {
            this.defaultExpiration = seconds;
        }

        public long getDefaultExpiration() {
            return this.defaultExpiration;
        }
    }

    static class CacheValueAccessor {
        private final RedisSerializer valueSerializer;

        CacheValueAccessor(RedisSerializer valueRedisSerializer) {
            this.valueSerializer = valueRedisSerializer;
        }

        byte[] convertToBytesIfNecessary(Object value) {
            if (value == null) {
                return new byte[0];
            } else {
                return this.valueSerializer == null && value instanceof byte[] ? (byte[]) ((byte[]) value) : this.valueSerializer.serialize(value);
            }
        }

        Object deserializeIfNecessary(byte[] value) {
            return this.valueSerializer != null ? this.valueSerializer.deserialize(value) : value;
        }
    }

    static class RedisCachePutIfAbsentCallback extends CustomizeRedisCache.AbstractRedisCacheCallback<byte[]> {
        public RedisCachePutIfAbsentCallback(CustomizeRedisCache.BinaryRedisCacheElement element, CustomizeRedisCache.RedisCacheMetadata metadata) {
            super(element, metadata);
        }

        public byte[] doInRedis(CustomizeRedisCache.BinaryRedisCacheElement element, RedisConnection connection) throws DataAccessException {
            this.waitForLock(connection);
            byte[] resultValue = this.put(element, connection);
            if (ObjectUtils.nullSafeEquals(element.get(), resultValue)) {
                this.processKeyExpiration(element, connection);
                this.maintainKnownKeys(element, connection);
            }

            return resultValue;
        }

        private byte[] put(CustomizeRedisCache.BinaryRedisCacheElement element, RedisConnection connection) {
            boolean valueWasSet = connection.setNX(element.getKeyBytes(), element.get()).booleanValue();
            return valueWasSet ? null : connection.get(element.getKeyBytes());
        }
    }

    static class RedisCacheEvictCallback extends CustomizeRedisCache.AbstractRedisCacheCallback<Void> {
        public RedisCacheEvictCallback(CustomizeRedisCache.BinaryRedisCacheElement element, CustomizeRedisCache.RedisCacheMetadata metadata) {
            super(element, metadata);
        }

        public Void doInRedis(CustomizeRedisCache.BinaryRedisCacheElement element, RedisConnection connection) throws DataAccessException {
            connection.del(new byte[][]{element.getKeyBytes()});
            this.cleanKnownKeys(element, connection);
            return null;
        }
    }

    static class RedisCachePutCallback extends CustomizeRedisCache.AbstractRedisCacheCallback<Void> {
        public RedisCachePutCallback(CustomizeRedisCache.BinaryRedisCacheElement element, CustomizeRedisCache.RedisCacheMetadata metadata) {
            super(element, metadata);
        }

        public Void doInRedis(CustomizeRedisCache.BinaryRedisCacheElement element, RedisConnection connection) throws DataAccessException {
            connection.multi();
            connection.set(element.getKeyBytes(), element.get());
            this.processKeyExpiration(element, connection);
            this.maintainKnownKeys(element, connection);
            connection.exec();
            return null;
        }
    }

    private static enum CacheValueRetrievalExceptionFactory {
        INSTANCE;

        private static boolean isSpring43 = ClassUtils.isPresent("org.springframework.cache.Cache$ValueRetrievalException", ClassUtils.getDefaultClassLoader());

        private CacheValueRetrievalExceptionFactory() {
        }

        public RuntimeException create(Object key, Callable<?> valueLoader, Throwable cause) {
            if (isSpring43) {
                try {
                    Class<?> execption = ClassUtils.forName("org.springframework.cache.Cache$ValueRetrievalException", this.getClass().getClassLoader());
                    Constructor<?> c = ClassUtils.getConstructorIfAvailable(execption, new Class[]{Object.class, Callable.class, Throwable.class});
                    return (RuntimeException) c.newInstance(key, valueLoader, cause);
                } catch (Exception var6) {
                    ;
                }
            }

            return new RedisSystemException(String.format("Value for key '%s' could not be loaded using '%s'.", key, valueLoader), cause);
        }
    }


    static class RedisCacheCleanByPrefixCallback extends CustomizeRedisCache.LockingRedisCacheCallback<Void> {
        private static final byte[] REMOVE_KEYS_BY_PATTERN_LUA = (new StringRedisSerializer()).serialize("local keys = redis.call('KEYS', ARGV[1]); local keysCount = table.getn(keys); if(keysCount > 0) then for _, key in ipairs(keys) do redis.call('del', key); end; end; return keysCount;");
        private static final byte[] WILD_CARD = (new StringRedisSerializer()).serialize("*");
        private final CustomizeRedisCache.RedisCacheMetadata metadata;

        public RedisCacheCleanByPrefixCallback(CustomizeRedisCache.RedisCacheMetadata metadata) {
            super(metadata);
            this.metadata = metadata;
        }

        public Void doInLock(RedisConnection connection) throws DataAccessException {
            byte[] prefixToUse = Arrays.copyOf(this.metadata.getKeyPrefix(), this.metadata.getKeyPrefix().length + WILD_CARD.length);
            System.arraycopy(WILD_CARD, 0, prefixToUse, this.metadata.getKeyPrefix().length, WILD_CARD.length);
            connection.eval(REMOVE_KEYS_BY_PATTERN_LUA, ReturnType.INTEGER, 0, new byte[][]{prefixToUse});
            return null;
        }
    }

    abstract static class LockingRedisCacheCallback<T> implements RedisCallback<T> {
        private final CustomizeRedisCache.RedisCacheMetadata metadata;

        public LockingRedisCacheCallback(CustomizeRedisCache.RedisCacheMetadata metadata) {
            this.metadata = metadata;
        }

        public T doInRedis(RedisConnection connection) throws DataAccessException {
            if (connection.exists(this.metadata.getCacheLockKey()).booleanValue()) {
                return null;
            } else {
                Object var2;
                try {
                    connection.set(this.metadata.getCacheLockKey(), this.metadata.getCacheLockKey());
                    var2 = this.doInLock(connection);
                } finally {
                    connection.del(new byte[][]{this.metadata.getCacheLockKey()});
                }

                return (T) var2;
            }
        }

        public abstract T doInLock(RedisConnection var1);
    }

    static class RedisCacheCleanByKeysCallback extends CustomizeRedisCache.LockingRedisCacheCallback<Void> {
        private static final int PAGE_SIZE = 128;
        private final CustomizeRedisCache.RedisCacheMetadata metadata;

        RedisCacheCleanByKeysCallback(CustomizeRedisCache.RedisCacheMetadata metadata) {
            super(metadata);
            this.metadata = metadata;
        }

        public Void doInLock(RedisConnection connection) {
            int offset = 0;
            boolean finished = false;

            do {
                Set<byte[]> keys = connection.zRange(this.metadata.getSetOfKnownKeysKey(), (long) (offset * 128), (long) ((offset + 1) * 128 - 1));
                finished = keys.size() < 128;
                ++offset;
                if (!keys.isEmpty()) {
                    connection.del((byte[][]) keys.toArray(new byte[keys.size()][]));
                }
            } while (!finished);

            connection.del(new byte[][]{this.metadata.getSetOfKnownKeysKey()});
            return null;
        }
    }


    static class BinaryRedisCacheElement extends RedisCacheElement {
        private byte[] keyBytes;
        private byte[] valueBytes;
        private RedisCacheElement element;
        private boolean lazyLoad;
        private CustomizeRedisCache.CacheValueAccessor accessor;

        public BinaryRedisCacheElement(RedisCacheElement element, CustomizeRedisCache.CacheValueAccessor accessor) {
            super(element.getKey(), element.get());
            this.element = element;
            this.keyBytes = element.getKeyBytes();
            this.accessor = accessor;
            this.lazyLoad = element.get() instanceof Callable;
            this.valueBytes = this.lazyLoad ? null : accessor.convertToBytesIfNecessary(element.get());
        }

        public byte[] getKeyBytes() {
            return this.keyBytes;
        }

        public long getTimeToLive() {
            return this.element.getTimeToLive();
        }

        public boolean hasKeyPrefix() {
            return this.element.hasKeyPrefix();
        }

        public boolean isEternal() {
            return this.element.isEternal();
        }

        public RedisCacheElement expireAfter(long seconds) {
            return this.element.expireAfter(seconds);
        }

        public byte[] get() {
            if (this.lazyLoad && this.valueBytes == null) {
                try {
                    this.valueBytes = this.accessor.convertToBytesIfNecessary(((Callable) this.element.get()).call());
                } catch (Exception var2) {
                    throw var2 instanceof RuntimeException ? (RuntimeException) var2 : new RuntimeException(var2.getMessage(), var2);
                }
            }

            return this.valueBytes;
        }
    }

    abstract static class AbstractRedisCacheCallback<T> implements RedisCallback<T> {
        private long WAIT_FOR_LOCK_TIMEOUT = 300L;
        private final CustomizeRedisCache.BinaryRedisCacheElement element;
        private final CustomizeRedisCache.RedisCacheMetadata cacheMetadata;

        public AbstractRedisCacheCallback(CustomizeRedisCache.BinaryRedisCacheElement element, CustomizeRedisCache.RedisCacheMetadata metadata) {
            this.element = element;
            this.cacheMetadata = metadata;
        }

        public T doInRedis(RedisConnection connection) throws DataAccessException {
            this.waitForLock(connection);
            return this.doInRedis(this.element, connection);
        }

        public abstract T doInRedis(CustomizeRedisCache.BinaryRedisCacheElement var1, RedisConnection var2) throws DataAccessException;

        protected void processKeyExpiration(RedisCacheElement element, RedisConnection connection) {
            if (!element.isEternal()) {
                connection.expire(element.getKeyBytes(), element.getTimeToLive());
            }

        }

        protected void maintainKnownKeys(RedisCacheElement element, RedisConnection connection) {
            if (!element.hasKeyPrefix()) {
                connection.zAdd(this.cacheMetadata.getSetOfKnownKeysKey(), 0.0D, element.getKeyBytes());
                if (!element.isEternal()) {
                    connection.expire(this.cacheMetadata.getSetOfKnownKeysKey(), element.getTimeToLive());
                }
            }

        }

        protected void cleanKnownKeys(RedisCacheElement element, RedisConnection connection) {
            if (!element.hasKeyPrefix()) {
                connection.zRem(this.cacheMetadata.getSetOfKnownKeysKey(), new byte[][]{element.getKeyBytes()});
            }

        }

        protected boolean waitForLock(RedisConnection connection) {
            boolean foundLock = false;

            boolean retry;
            do {
                retry = false;
                if (connection.exists(this.cacheMetadata.getCacheLockKey()).booleanValue()) {
                    foundLock = true;

                    try {
                        Thread.sleep(this.WAIT_FOR_LOCK_TIMEOUT);
                    } catch (InterruptedException var5) {
                        Thread.currentThread().interrupt();
                    }

                    retry = true;
                }
            } while (retry);

            return foundLock;
        }

        protected void lock(RedisConnection connection) {
            this.waitForLock(connection);
            connection.set(this.cacheMetadata.getCacheLockKey(), "locked".getBytes());
        }

        protected void unlock(RedisConnection connection) {
            connection.del(new byte[][]{this.cacheMetadata.getCacheLockKey()});
        }
    }

    static class RedisWriteThroughCallback extends CustomizeRedisCache.AbstractRedisCacheCallback<byte[]> {
        public RedisWriteThroughCallback(CustomizeRedisCache.BinaryRedisCacheElement element, CustomizeRedisCache.RedisCacheMetadata metadata) {
            super(element, metadata);
        }

        public byte[] doInRedis(CustomizeRedisCache.BinaryRedisCacheElement element, RedisConnection connection) throws DataAccessException {
            byte[] var4;
            try {
                this.lock(connection);

                try {
                    byte[] value = connection.get(element.getKeyBytes());
                    if (value != null) {
                        var4 = value;
                        return var4;
                    }

                    connection.watch(new byte[][]{element.getKeyBytes()});
                    connection.multi();
                    value = element.get();
                    connection.set(element.getKeyBytes(), value);
                    this.processKeyExpiration(element, connection);
                    this.maintainKnownKeys(element, connection);
                    connection.exec();
                    var4 = value;
                } catch (RuntimeException var8) {
                    connection.discard();
                    throw var8;
                }
            } finally {
                this.unlock(connection);
            }

            return var4;
        }
    }

}



可以关注来获取对应的源码

file-list

Tags: