All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.bingoohuang.springrediscache.InvocationRuntime Maven / Gradle / Ivy

The newest version!
package com.github.bingoohuang.springrediscache;

import com.github.bingoohuang.utils.redis.Redis;
import net.jodah.expiringmap.ExpiringMap;
import org.aopalliance.intercept.MethodInvocation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.util.StringUtils;

import java.util.NoSuchElementException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import static net.jodah.expiringmap.ExpiringMap.ExpirationPolicy.CREATED;

class InvocationRuntime {
    private final MethodInvocation invocation;
    private final RedisCacheEnabled redisCacheAnn;
    private final String valueKey;
    private final String lockKey;
    private final Redis redis;
    private final ExpiringMap localCache;
    private final ApplicationContext appContext;
    private final ScheduledExecutorService executorService;
    final Logger logger;
    private final ValueSerializable valueSerializer;
    private Object value;

    InvocationRuntime(MethodInvocation invocation, ExpiringMap localCache,
                      ApplicationContext appContext, ScheduledExecutorService executorService) {
        this.logger = LoggerFactory.getLogger(invocation.getMethod().getDeclaringClass());
        this.invocation = invocation;
        this.redisCacheAnn = invocation.getMethod().getAnnotation(RedisCacheEnabled.class);
        this.redis = RedisCacheUtils.tryGetBean(appContext, Redis.class);
        this.localCache = localCache;
        this.appContext = appContext;
        this.executorService = executorService;

        this.valueKey = RedisCacheUtils.generateValueKey(invocation, redisCacheAnn, appContext, logger);
        this.valueSerializer = RedisCacheUtils.createValueSerializer(appContext, redisCacheAnn, invocation.getMethod(), logger);
        this.lockKey = valueKey + ":lock";
    }

    long expirationSeconds() {
        return redisCacheAnn.expirationSeconds();
    }

    Object invokeMethod() {
        this.value = RedisCacheUtils.invokeMethod(invocation, appContext);
        return this.value;
    }

    boolean tryRedisLock() {
        boolean locked = redis.tryLock(lockKey);
        if (locked) logger.debug("got redis lock {}", lockKey);
        return locked;
    }

    void unlockRedis(boolean locked) {
        if (locked) {
            redis.del(lockKey);
            logger.debug("free redis lock {}", lockKey);
        }
    }

    void setex(long expirationSeconds) {
        logger.debug("put redis {} = {} with expiration {} seconds", valueKey, value, expirationSeconds);

        String value = valueSerializer.serialize(this.value);
        redis.setex(valueKey, value, expirationSeconds, TimeUnit.SECONDS);
    }

    Object getLocalCache() {
        CachedValueWrapper cachedValue = localCache.get(valueKey);
        if (cachedValue != null) {
            long expectedExpiration = getExpectedExpirationSeconds();
            logger.debug("got local {} = {} with expiration {} seconds", valueKey, cachedValue.getValue(), expectedExpiration);
            if (expectedExpiration >= 0) this.value = cachedValue.getValue();
            else localCache.remove(valueKey);
        } else {
            logger.debug("got local {} = null", valueKey);
        }

        return this.value;
    }

    private long getExpectedExpirationSeconds() {
        try {
            return localCache.getExpectedExpiration(valueKey) / 1000;
        } catch (NoSuchElementException e) {
            return -1;
        }
    }

    long redisTtlSeconds() {
        return redis.ttl(valueKey);
    }

    void putLocalCache(long ttlSeconds) {
        logger.debug("put local {} = {} with expiration {} seconds", valueKey, this.value, ttlSeconds);
        CachedValueWrapper valueWrapper = new CachedValueWrapper(this.value, redisCacheAnn, logger);
        localCache.put(valueKey, valueWrapper, CREATED, ttlSeconds, TimeUnit.SECONDS);
    }

    void waitLockReleaseAndReadRedis() {
        waitRedisLock();
        loadRedisValueToCache(-1);
    }

    Object getValue() {
        return value;
    }

    void loadRedisValueToCache(long ttlSeconds) {
        String redisValue = redis.get(valueKey);
        logger.debug("got redis {} = {}", valueKey, redisValue);

        if (StringUtils.isEmpty(redisValue)) value = null;
        else {
            value = valueSerializer.deserialize(redisValue, invocation.getMethod());
            if (value != null) putLocalCache(ttlSeconds > 0 ? ttlSeconds : redisTtlSeconds());
        }
    }

    boolean isBeforeAheadSeconds() {
        return getExpectedExpirationSeconds() > Consts.AheadRefreshSeconds;
    }

    boolean isAheadRefreshEnabled() {
        return redisCacheAnn.aheadRefresh() &&
                redisCacheAnn.expirationSeconds() > Consts.AheadRefreshSeconds;
    }

    Object process() {
        checkRedisRequired();
        tryInvalidCacheIfConnectingCache();

        CacheProcessor cacheProcessor = getCacheProcessor();
        return cacheProcessor.process();
    }

    private CacheProcessor getCacheProcessor() {
        switch (redisCacheAnn.redisFor()) {
            case StoreValue:
                return new StoreValueProcessor(this);
            case RefreshSeconds:
            case CwdFileRefreshSeconds:
            default:
                return new RefreshSecondsProcessor(this);
        }
    }

    private void checkRedisRequired() {
        switch (redisCacheAnn.redisFor()) {
            case StoreValue:
            case RefreshSeconds:
                if (redis != null) return;
                throw new RuntimeException("Redis bean should defined in spring context");
        }
    }

    private void tryInvalidCacheIfConnectingCache() {
        Object threadLocalValue = RedisCacheConnector.threadLocal.get();
        if (threadLocalValue == null) return;

        localCache.remove(valueKey);
        if (redisCacheAnn.redisFor() == RedisFor.StoreValue) redis.del(valueKey);
    }


    void submit(Runnable runnable) {
        executorService.submit(runnable);
    }

    boolean tryLockLocalCache() {
        Object prev = localCache.putIfAbsent(lockKey, CachedValueWrapper.instance);
        return prev == null;
    }

    void unlockLocalCache() {
        localCache.remove(lockKey);
        logger.debug("free local lock {}", lockKey);
    }

    Object invokeMethodAndPutCache() {
        invokeMethod();
        if (value != null) {
            long expiration = RedisCacheUtils.redisExpirationSeconds(valueKey, appContext);
            putLocalCache(expiration);
        }

        return value;
    }

    void waitCacheLock() {
        logger.debug("wait cache lock {}", lockKey);
        while (!tryLockLocalCache()) {
            RedisCacheUtils.sleep(100);
        }
        logger.debug("got cache lock {}", lockKey);
    }

    private void waitRedisLock() {
        logger.debug("wait redis lock {}", lockKey);
        do {
            RedisCacheUtils.sleep(100);
        } while (redis.isLocked(lockKey));
        logger.debug("got redis lock {}", lockKey);
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy