The basic structure of the Cache class library

Second, the Cache

  • Similar to List in collections, it defines some basic methods for caching, such as get and PUT.
  • Provides a default implementation of methods, using the template method pattern, lowercase methods have a default implementation, uppercase methods are implemented by subclasses.
  • The lower case method returns data, but when the method returns NULL, there is no way to tell whether the key does not exist, is out of date, or there was an exception when accessing the cache. The upper case GET method provides complete information.
  • Template method design pattern: Defines the skeleton of an algorithm in an operation, deferring some steps to subclasses. The template approach allows subclasses to redefine specific steps of an algorithm without changing the structure of that algorithm.
default V get(K key) throws CacheInvokeException { CacheGetResult<V> result = GET(key); If (result.issuccess ()) {return result.getValue(); } else { return null; } } CacheGetResult<V> GET(K key); // interface method, to be subclass implementationCopy the code

Third, AbstractCache

  • The cache interface is implemented, similar to AbstractList, but different from AbstractList, the design pattern of template method is still adopted, which is equivalent to extracting some common parts in this layer and defining interface methods to be implemented in subclasses.
  • ArrayList heavily overwrites the method implementations provided by AbstractList. So, AbstractList doesn’t mean much for ArrayList, but more for other subclasses of AbstractList.

AbstractList abstract class AbstractList abstract class AbstractList abstract class AbstractList

  1. Increased readability, you can clearly see the main implementation interface of the class.
  2. Reduce maintenance costs, and I won’t be affected if AbstractList doesn’t implement the List interface one day.
  • In this method, it can be seen that the statistics are the execution time of the program, and the network IO time will not be counted.
  • Execute the asynchronous method to create a new cached event and send a notification to the monitored object that cached the event.
@Override public final CacheGetResult<V> GET(K key) { long t = System.currentTimeMillis(); CacheGetResult<V> result; if (key == null) { result = new CacheGetResult<V>(CacheResultCode.FAIL, CacheResult.MSG_ILLEGAL_ARGUMENT, null); } else { result = do_GET(key); } result.future().thenRun(() -> { CacheGetEvent event = new CacheGetEvent(this, System.currentTimeMillis() - t, key, result); notify(event); }); return result; } protected abstract CacheGetResult<V> do_GET(K key); // Template methods, implemented by subclassesCopy the code

Asynchronous implementation: A private instance variable is maintained in CacheResult to perform asynchronous operations, and you can see that the future variable is either passed in as an argument in the CacheResult constructor or initialized in the constructor. Calling the Future () method exposes this object to perform the asynchronous operation. This code calls the thenRun() method of the CompletionStage, which executes the next operation without caring about the result of the previous calculation.

    private CompletionStage<ResultData> future;

    public CacheResult(CompletionStage<ResultData> future) {
        this.future = future;
    }

    public CacheResult(CacheResultCode resultCode, String message) {
        this(CompletableFuture.completedFuture(new ResultData(resultCode, message, null)));
    }

    public CacheResult(Throwable ex) {
        future = CompletableFuture.completedFuture(new ResultData(ex));
    }
    
    public CompletionStage<ResultData> future() {
        return future;
    }
Copy the code

Sending notification method:

Public void notify(CacheEvent e) {List<CacheMonitor> monitors = config().getmonitors (); for (CacheMonitor m : monitors) { m.afterOperation(e); }}Copy the code

ComputeIfAbsentImpl method:

  • This method translates as: calculate if the value is not present. So if the cache doesn’t get a value, we put a value in.
// Loader static <K, V> V computeIfAbsentImpl(K key, Function<K, V> loader, Function<K, V> boolean cacheNullWhenLoaderReturnNull, long expireAfterWrite, TimeUnit timeUnit, Cache<K, V > cache) {/ / strong cache for AbstractCache type AbstractCache < K, V > AbstractCache = CacheUtil. GetAbstractCache (cache); // Use proxy mode to build a Loader, Timing is used for the load process CacheLoader < K, V > newLoader = CacheUtil. CreateProxyLoader (cache, loader, abstractCache: : notify); CacheGetResult<V> r; // Set different cache value acquisition strategies according to the cache type. CacheGetResult if (cache instanceof RefreshCache) {RefreshCache<K, V> RefreshCache = ((RefreshCache<K, V>) cache); r = refreshCache.GET(key); refreshCache.addOrUpdateRefreshTask(key, newLoader); } else { r = cache.GET(key); } // Check whether the cache is obtained. If not, check whether the cache needs to be updated. if (r.isSuccess()) { return r.getValue(); } else { Consumer<V> cacheUpdater = (loadedValue) -> { if(needUpdate(loadedValue, cacheNullWhenLoaderReturnNull, newLoader)) { if (timeUnit ! = null) { cache.PUT(key, loadedValue, expireAfterWrite, timeUnit).waitForResult(); } else { cache.PUT(key, loadedValue).waitForResult(); }}}; V loadedValue; / / cache through protecting the if (cache. Config () isCachePenetrationProtect ()) = {loadedValue synchronizedLoad (cache. The config (), abstractCache, key, newLoader, cacheUpdater); } else { loadedValue = newLoader.apply(key); // A functional interface that consumes data cacheupater. Accept (loadedValue); } return loadedValue; }}Copy the code
  • This method uses a functional interface to pass loader objects, using proxy mode, in order to load the elapsed time.
  • The main reason for using the proxy pattern is to allow developers to have different implementations, but not necessarily to write time-consuming code every time, pulling out the common parts of the code.
    public static <K, V> ProxyLoader<K, V> createProxyLoader(Cache<K, V> cache,
                                                          Function<K, V> loader,
                                                          Consumer<CacheEvent> eventConsumer) {
        if (loader instanceof ProxyLoader) {
            return (ProxyLoader<K, V>) loader;
        }
        if (loader instanceof CacheLoader) {
            return createProxyLoader(cache, (CacheLoader) loader, eventConsumer);
        }
        return k -> {
            long t = System.currentTimeMillis();
            V v = null;
            boolean success = false;
            try {
                v = loader.apply(k);
                success = true;
            } finally {
                t = System.currentTimeMillis() - t;
                CacheLoadEvent event = new CacheLoadEvent(cache, t, k, v, success);
                eventConsumer.accept(event);
            }
            return v;
        };
    }
Copy the code