springmvc redis @Cacheable擴充套件(一)

要吃西藍花發表於2021-03-09

springmvc 中有自帶的cache處理模組,可以是方法級別的快取處理,那麼在實際使用中,很可能自己造輪子,因為實際中永遠會有更奇怪的需求點。比如:
1 清除快取時候,能模糊的進行刪除
2 針對不同的key,設定不同的過期時間
這2個是有些麻煩的需求,當然針對快取內容,設定 key(這個 key 的確定)更讓人難受,不好取捨,需要有一定的開發經驗,否則只能不停的修改。
我們先集中處理第一個問題,模糊刪除

  • 查詢方案
  • 檢視低版本redis實現
  • 具體處理方式

明確問題,查詢方案

可能網上有不少的解決方案

  1. 直接重寫 https://blog.csdn.net/Crystalqy/article/details/110681684
  2. spring 5 + 版本的 https://my.oschina.net/u/220938/blog/3196609
  3. 具有啟發性的 https://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

相關文章