Java geek


Related reading:

JetCache source code (2) Top view JetCache source code (3) Cache class structure and code parsing 1 JAVA basics (1) A simple, thorough understanding of inner classes and static inner classes JAVA Foundation (2) Memory optimization – Using JAVA references for caching JAVA foundation (3) Hot loading JAVA foundation (4) Enumeration (enum) and constant definition, factory class use contrast JAVA foundation (5) Functional interface – reuse, JAVA programming ideas (1) Increase extensibility through dependency Injection (2) How to program for interface (3) Remove awkward if, self-registration strategy mode elegant meet the open and close principle (4) JAVA programming ideas (Builder mode) classical paradigm and factory mode how to choose? HikariPool source code (two) design ideas for reference in the workplace (a) IT factory survival rules


1. Class structure

Abstract: AbstractEmbeddedCache contains two AbstractEmbeddedCache implementations, LRU cache and Caffeine cache

As always, the template pattern is used, but instead of implementing the template method directly in the subclass, the subclass creates the InnerMap instance, and then calls the InnerMap method at the call point of the template method: Keep need to subclass implementation of template method cohesion into a class, and relatively independent (directly in the subclass implementation also cohesive, but not independent), after the class independent, later if you want to expand, can be finished through independent factory class creation process, the factory class can also through dependency injection, greatly improving the flexibility and extensibility.

2. Core code analysis

2.1. AbstractEmbeddedCache. Java

AbstractEmbeddedCache implements some common methods to avoid repeated implementation by subclasses, and uses a template pattern to allow subclasses to implement the change-point part.

public abstract class AbstractEmbeddedCache<K.V> extends AbstractCache<K.V> {
    // has its own configuration class
    protected EmbeddedCacheConfig<K, V> config;
    // Holds the InnerMap interface to which template methods are delegated
    protected InnerMap innerMap;

    // The subclass creates the InnerMap, making the template methods to be called cohesive and independent
    protected abstract InnerMap createAreaCache(a);

    public AbstractEmbeddedCache(EmbeddedCacheConfig<K, V> config) {
        this.config = config;
        // Template method pattern, implemented by subclasses
        innerMap = createAreaCache();
    }

    @Override
    public CacheConfig<K, V> config(a) {
        return config;
    }

    // Public method
    public Object buildKey(K key) {
        Object newKey = key;
        Function<K, Object> keyConvertor = config.getKeyConvertor();
        if(keyConvertor ! =null) {
            newKey = keyConvertor.apply(key);
        }
        return newKey;
    }

    @Override
    protected CacheGetResult<V> do_GET(K key) {
        Object newKey = buildKey(key);
        // Delegate to innerMap. You can also define a template method, which is implemented by subclasses, but innerMap brings all the template methods to be implemented together and is more independent
        CacheValueHolder<V> holder = (CacheValueHolder<V>) innerMap.getValue(newKey);
        return parseHolderResult(holder);
    }

    protected CacheGetResult<V> parseHolderResult(CacheValueHolder<V> holder) {
        long now = System.currentTimeMillis();
        if (holder == null) {
            return CacheGetResult.NOT_EXISTS_WITHOUT_MSG;
        } else if (now >= holder.getExpireTime()) {
            return CacheGetResult.EXPIRED_WITHOUT_MSG;
        } else {
            synchronized (holder) {
                long accessTime = holder.getAccessTime();
                if (config.isExpireAfterAccess()) {
                    long expireAfterAccess = config.getExpireAfterAccessInMillis();
                    if (now >= accessTime + expireAfterAccess) {
                        return CacheGetResult.EXPIRED_WITHOUT_MSG;
                    }
                }
                holder.setAccessTime(now);
            }

            return new CacheGetResult(CacheResultCode.SUCCESS, null, holder); }}@Override
    protected MultiGetResult<K, V> do_GET_ALL(Set<? extends K> keys) {
        ArrayList<K> keyList = new ArrayList<K>(keys.size());
        ArrayList<Object> newKeyList = new ArrayList<Object>(keys.size());
        keys.stream().forEach((k) -> {
            Object newKey = buildKey(k);
            keyList.add(k);
            newKeyList.add(newKey);
        });
        // Still delegate to innerMap
        Map<Object, CacheValueHolder<V>> innerResultMap = innerMap.getAllValues(newKeyList);
        Map<K, CacheGetResult<V>> resultMap = new HashMap<>();
        for (int i = 0; i < keyList.size(); i++) {
            K key = keyList.get(i);
            Object newKey = newKeyList.get(i);
            CacheValueHolder<V> holder = innerResultMap.get(newKey);
            resultMap.put(key, parseHolderResult(holder));
        }
        MultiGetResult<K, V> result = new MultiGetResult<>(CacheResultCode.SUCCESS, null, resultMap);
        return result;
    }
    
    // The last few methods are also common code + template methods that delegate to the innerMap call
Copy the code

2.2. LinkedHashMapCache. Java

Key points of 2.2.1.

  • In LinkedHashMapCache, we define an inner class, LRUMap, that implements the InnerMap interface. LRUMap implements an LRU cache, which is cleaned up if it expires.

  • Because LRUMap is an inner class, the locking object passed in the constructor of LRUMap can be defined directly in the LinkedHashMapCache, and the purpose of locking the LinkedHashMapCache instance can also be achieved. Passing in the LRUMap constructor is not necessary because the locking scenario is determined. Passing in through the constructor adds to the burden of understanding.

  • When programming for interfaces, because there are more methods in subclasses than interfaces, it is always uncomfortable to be forced to use strong conversions. You can use non-strong conversions as follows:
  •     // Define a member variable
        private LRUMap lruMap;
        
        @Override
        protected InnerMap createAreaCache(a) {
            // Assign to the private member first, then return.
            lruMap = new LRUMap(config.getLimit(), this);
            return lruMap;
        }
        
        public void cleanExpiredEntry(a) {
            // This does not require strong rotation
            lruMap.cleanExpiredEntry();
        }
    Copy the code

    2.2.2. Code

    package com.alicp.jetcache.embedded;
    
    import com.alicp.jetcache.CacheResultCode;
    import com.alicp.jetcache.CacheValueHolder;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import java.util.*;
    
    / * * *@author <a href="mailto:[email protected]">huangli</a>
     */
    public class LinkedHashMapCache<K.V> extends AbstractEmbeddedCache<K.V> {
    
        private static Logger logger = LoggerFactory.getLogger(LinkedHashMapCache.class);
    
        public LinkedHashMapCache(EmbeddedCacheConfig<K, V> config) {
            super(config);
            addToCleaner();
        }
    
        protected void addToCleaner(a) {
            Cleaner.add(this);
        }
    
        @Override
        protected InnerMap createAreaCache(a) {
            // Define a private LRUMap member, assign it to the private member, and return it, instead of forcing it in the cleanExpiredEntry method below
            return new LRUMap(config.getLimit(), this);
        }
    
        @Override
        public <T> T unwrap(Class<T> clazz) {
            if (clazz.equals(LinkedHashMap.class)) {
                return (T) innerMap;
            }
            throw new IllegalArgumentException(clazz.getName());
        }
    
        public void cleanExpiredEntry(a) {
            // When programming for interfaces, it feels awkward to be forced to use strongcasts because there are more methods in subclasses than in interfaces
            // To avoid overcasting, you can define a private LRUMap member and copy it to it while creating the InnerMap, which is called by LRUMap's private person
            ((LRUMap) innerMap).cleanExpiredEntry();
        }
    
        / / inner classes
        final class LRUMap extends LinkedHashMap implements InnerMap {
    
            private final int max;
            // Can be defined directly in LinkedHashMapCache and referenced directly, without passing in the constructor
            private Object lock;
    
            LinkedHashMapCache; // This lock is an instance of LinkedHashMapCache.
            // We want to lock based on the LinkedHashMapCache instance, although the actual locking is only used in the LRUMap instance method. To avoid further extension,
            // Based on the principle of minimal visibility, it is also possible to define a lock directly in the LinkedHashMapCache, without passing it in through the constructor.
            public LRUMap(int max, Object lock) {
                super((int) (max * 1.4 f), 0.75 f.true);
                this.max = max;
                this.lock = lock;
            }
    
            @Override
            protected boolean removeEldestEntry(Map.Entry eldest) {
                return size() > max;
            }
    
            // Clear the stale cache
            void cleanExpiredEntry(a) {
                synchronized (lock) {
                    for (Iterator it = entrySet().iterator(); it.hasNext();) {
                        Map.Entry en = (Map.Entry) it.next();
                        Object value = en.getValue();
                        if(value ! =null && value instanceof CacheValueHolder) {
                            CacheValueHolder h = (CacheValueHolder) value;
                            logger.info("CacheValueHolder: " + h.toString());
                            if(System.currentTimeMillis() >= h.getExpireTime()) { it.remove(); }}else {
                            // assert false
                            if (value == null) {
                                logger.error("key " + en.getKey() + " is null");
                            } else {
                                logger.error("value of key " + en.getKey() + " is not a CacheValueHolder. type=" + value.getClass());
                            }
                        }
                    }
                }
            }
    
            @Override
            public Object getValue(Object key) {
                synchronized (lock) {
                    returnget(key); }}@Override
            public Map getAllValues(Collection keys) {
                Map values = new HashMap();
                synchronized (lock) {
                    for (Object key : keys) {
                        Object v = get(key);
                        if(v ! =null) { values.put(key, v); }}}return values;
            }
    
            @Override
            public void putValue(Object key, Object value) {
                synchronized(lock) { put(key, value); }}@Override
            public void putAllValues(Map map) {
                synchronized (lock) {
                    Set<Map.Entry> set = map.entrySet();
                    for(Map.Entry en : set) { put(en.getKey(), en.getValue()); }}}@Override
            public boolean removeValue(Object key) {
                synchronized (lock) {
                    returnremove(key) ! =null; }}@Override
            public void removeAllValues(Collection keys) {
                synchronized (lock) {
                    for(Object k : keys) { remove(k); }}}@Override
            @SuppressWarnings("unchecked")
            public boolean putIfAbsentValue(Object key, Object value) {
                synchronized (lock) {
                    CacheValueHolder h = (CacheValueHolder) get(key);
                    if (h == null || parseHolderResult(h).getResultCode() == CacheResultCode.EXPIRED) {
                        put(key, value);
                        return true;
                    } else {
                        return false;
                    }
                }
            }
        }
    }
    Copy the code

    2.3. CaffeineCache. Java

    Key points of 2.3.1.

  • An anonymous inner class is used to create an LRUMap instance that holds Caffeine’s Cache instance, to which all method calls are ultimately delegated.

  • The Builder design pattern is used and the default implementation is overridden through dependency injection.

  • 2.3.2. Code

    package com.alicp.jetcache.embedded;
    
    import com.alicp.jetcache.CacheValueHolder;
    import com.github.benmanes.caffeine.cache.Caffeine;
    import com.github.benmanes.caffeine.cache.Expiry;
    
    import java.util.Collection;
    import java.util.Map;
    import java.util.concurrent.TimeUnit;
    
    /**
     * Created on 2016/10/25.
     *
     * @author <a href="mailto:[email protected]">huangli</a>
     */
    public class CaffeineCache<K.V> extends AbstractEmbeddedCache<K.V> {
        // Holds caffeine's cache instance to which method calls are ultimately delegated
        private com.github.benmanes.caffeine.cache.Cache cache;
    
        public CaffeineCache(EmbeddedCacheConfig<K, V> config) {
            super(config);
        }
    
        @Override
        public <T> T unwrap(Class<T> clazz) {
            if (clazz.equals(com.github.benmanes.caffeine.cache.Cache.class)) {
                return (T) cache;
            }
            throw new IllegalArgumentException(clazz.getName());
        }
    
        @Override
        @SuppressWarnings("unchecked")
        protected InnerMap createAreaCache(a) {
            // build a model
            Caffeine<Object, Object> builder = Caffeine.newBuilder();
            builder.maximumSize(config.getLimit());
            final boolean isExpireAfterAccess = config.isExpireAfterAccess();
            final long expireAfterAccess = config.getExpireAfterAccessInMillis();
            // Override the default by dependency injection
            builder.expireAfter(new Expiry<Object, CacheValueHolder>() {
                private long getRestTimeInNanos(CacheValueHolder value) {
                    long now = System.currentTimeMillis();
                    long ttl = value.getExpireTime() - now;
                    if(isExpireAfterAccess){
                        ttl = Math.min(ttl, expireAfterAccess);
                    }
                    return TimeUnit.MILLISECONDS.toNanos(ttl);
                }
    
                @Override
                public long expireAfterCreate(Object key, CacheValueHolder value, long currentTime) {
                    return getRestTimeInNanos(value);
                }
    
                @Override
                public long expireAfterUpdate(Object key, CacheValueHolder value,
                                              long currentTime, long currentDuration) {
                    return currentDuration;
                }
    
                @Override
                public long expireAfterRead(Object key, CacheValueHolder value,
                                            long currentTime, long currentDuration) {
                    returngetRestTimeInNanos(value); }}); cache = builder.build();// Anonymous inner classes are used because the invocation relationship is relatively simple
            return new InnerMap() {
                @Override
                public Object getValue(Object key) {
                    return cache.getIfPresent(key);
                }
    
                @Override
                public Map getAllValues(Collection keys) {
                    return cache.getAllPresent(keys);
                }
    
                @Override
                public void putValue(Object key, Object value) {
                    cache.put(key, value);
                }
    
                @Override
                public void putAllValues(Map map) {
                    cache.putAll(map);
                }
    
                @Override
                public boolean removeValue(Object key) {
                    returncache.asMap().remove(key) ! =null;
                }
    
                @Override
                public void removeAllValues(Collection keys) {
                    cache.invalidateAll(keys);
                }
    
                @Override
                public boolean putIfAbsentValue(Object key, Object value) {
                    return cache.asMap().putIfAbsent(key, value) == null; }}; }}Copy the code

    3. Summary

    1. When using the template method design pattern, all the template methods to be implemented can be clustered into a class, that is, the cohesive and relatively independent, coupled with injectable factory classes, which can further improve scalability.





    2. Dependency injection is ubiquitous and a necessary pattern to provide high extensibility.





    3. Interface oriented programming, but also to avoid strong transfer, the implementation is worth thinking about.





    4. Builder mode compared to factory mode to create instances, is also very common, is a must design mode.


    end.


    <– Read the mark, left like!