springmvc 中有自帶的cache處理模組,可以是方法級別的快取處理,那麼在實際使用中,很可能自己造輪子,因為實際中永遠會有更奇怪的需求點。比如:
1 清除快取時候,能模糊的進行刪除
2 針對不同的key,設定不同的過期時間
這2個是有些麻煩的需求,當然針對快取內容,設定 key(這個 key 的確定)更讓人難受,不好取捨,需要有一定的開發經驗,否則只能不停的修改。
我們先集中處理第一個問題,模糊刪除
- 查詢方案
- 檢視低版本redis實現
- 具體處理方式
明確問題,查詢方案
可能網上有不少的解決方案
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 等引數,那麼,點進去看原始碼,就會發現
CustomizedRedisCacheManager
和 CustomizeRedisCache
,和 高版本的名字很像,那麼仔細看看,發現 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;
}
}
}
可以關注來獲取對應的原始碼