【极客源码】JetCache源码(四)AbstractEmbeddedCache相关类结构

Java极客  |  作者  /  铿然一叶

这是Java极客的第 74 篇原创文章


相关阅读:

【极客源码】JetCache源码(一)开篇
【极客源码】JetCache源码(二)顶层视图
【极客源码】JetCache源码(三)Cache类结构和代码解析1
JAVA基础(一)简单、透彻理解内部类和静态内部类
JAVA基础(二)内存优化-使用Java引用做缓存
JAVA基础(三)ClassLoader实现热加载
JAVA基础(四)枚举(enum)和常量定义,工厂类使用对比
JAVA基础(五)函数式接口-复用,解耦之利刃
JAVA编程思想(一)通过依赖注入增加扩展性
JAVA编程思想(二)如何面向接口编程
JAVA编程思想(三)去掉别扭的if,自注册策略模式优雅满足开闭原则
JAVA编程思想(四)Builder模式经典范式以及和工厂模式如何选?
HikariPool源码(二)设计思想借鉴
人在职场(一)IT大厂生存法则


1. 类结构

1.AbstractEmbeddedCache下面有两个缓存实现,分别是LRU缓存Caffeine缓存

2.一如既往的,模板模式被使用,只不过这里的模板方法不是直接在子类实现,而是通过子类创建InnerMap实例,再在模板方法的调用点调用InnerMap实例的相应方法,这么做的目的是:让需要子类实现的模板方法都内聚到一个类中,并且相对独立(直接在子类实现也内聚,但是不够独立),当类独立后,后续如果要扩展,可以通过独立的工厂类来完成创建过程,工厂类还可以通过依赖注入,大大提高了灵活性,扩展性。

2. 核心代码解析

2.1. AbstractEmbeddedCache.java

AbstractEmbeddedCache实现了一些公共方法,避免子类重复实现,并且采用模板模式,由子类完成变化点部分的实现。

public abstract class AbstractEmbeddedCache<K, V> extends AbstractCache<K, V> {
    // 有自己的配置类
    protected EmbeddedCacheConfig<K, V> config;
    // 持有InnerMap接口,模板方法都委托给它
    protected InnerMap innerMap;
    // 由子类来创建InnerMap,使得要调用得模板方法内聚且独立
    protected abstract InnerMap createAreaCache();
    public AbstractEmbeddedCache(EmbeddedCacheConfig<K, V> config) {
        this.config = config;
        // 模板方法模式,由子类实现
        innerMap = createAreaCache();
    }
    @Override
    public CacheConfig<K, V> config() {
        return config;
    }
    // 公共方法
    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);
        // 委托给innerMap,也可以是定义一个模板方法,由子类实现,但innerMap可将所有待实现模板方法内聚到一起,也更独立
        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);
        });
        // 仍然是委托给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;
    }
    
    // 后面几个方法也都是公共代码+模板方法,委托给innerMap调用,不在详述

2.2. LinkedHashMapCache.java

2.2.1. 要点

  • 在LinkedHashMapCache中定义了一个内部类LRUMap,并实现InnerMap接口;LRUMap实现了一个LRU缓存,如果缓存过期,则清理掉。
  • 因为LRUMap是内部类,其构造器中传入的加锁对象可以直接在LinkedHashMapCache中定义,同样可以实现对LinkedHashMapCache类实例加锁的目的,通过LRUMap构造器传入,不是很有必要,因为加锁场景是确定的,通过构造器传入会增加理解负担
  • 面向接口编程时,由于子类的方法比接口多,被迫使用强转总感觉有些别扭,可以使用非强转方式实现如下:
  •     // 定义一个成员变量
        private LRUMap lruMap;
        
        @Override
        protected InnerMap createAreaCache() {
            // 先赋值给私有成员,再返回.
            lruMap = new LRUMap(config.getLimit(), this);
            return lruMap;
        }
        
        public void cleanExpiredEntry() {
            // 这样不需要强转
            lruMap.cleanExpiredEntry();
        }
    

    2.2.2. 代码

    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:areyouok@gmail.com">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() {
            Cleaner.add(this);
        }
        @Override
        protected InnerMap createAreaCache() {
            // 这里先定义一个私有的LRUMap成员,先赋值给私有成员,再返回,下面的cleanExpiredEntry方法中就不用强转
            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() {
            // 面向接口编程时,由于子类的方法比接口多,被迫使用强转总感觉有些别扭
            // 要避免强转,可以定义一个私有的LRUMap成员,在创建InnerMap时,同时复制给它,这里通过LRUMap的私有人员来调用
            ((LRUMap) innerMap).cleanExpiredEntry();
        }
        // 内部类
        final class LRUMap extends LinkedHashMap implements InnerMap {
            private final int max;
            // 可以直接定义在LinkedHashMapCache中并直接引用,不需要通过构造器传入
            private Object lock;
            // 从实际调用情况来看,这个lock是LinkedHashMapCache类实例,之所以用LinkedHashMapCache类实例,
            // 是想基于LinkedHashMapCache实例加锁,尽管当前实际只是在LRUMap实例方法中用到了加锁,为了避免后续扩展,
            // 基于最小可见性原则,也可以直接在LinkedHashMapCache中定义一个锁来使用,而不需要通过构造器传入。
            public LRUMap(int max, Object lock) {
                super((int) (max * 1.4f), 0.75f, true);
                this.max = max;
                this.lock = lock;
            }
            @Override
            protected boolean removeEldestEntry(Map.Entry eldest) {
                return size() > max;
            }
            // 清理失效缓存
            void cleanExpiredEntry() {
                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) {
                    return get(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) {
                    return remove(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;
                    }
                }
            }
        }
    }
    

    2.3. CaffeineCache.java

    2.3.1. 要点

  • 通过匿名内部类来创建了LRUMap实例,该实例持有Caffeine的Cache实例,所有方法调用最终都委托给Cache实例。
  • 使用了Builder设计模式,并通过依赖注入重载了默认实现。
  • 2.3.2. 代码

    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:areyouok@gmail.com">huangli</a>
     */
    public class CaffeineCache<K, V> extends AbstractEmbeddedCache<K, V> {
        // 持有caffeine的缓存实例,方法调用最终都委托给它
        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() {
            // Builder设计模式
            Caffeine<Object, Object> builder = Caffeine.newBuilder();
            builder.maximumSize(config.getLimit());
            final boolean isExpireAfterAccess = config.isExpireAfterAccess();
            final long expireAfterAccess = config.getExpireAfterAccessInMillis();
            // 通过依赖注入重载默认实现
            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) {
                    return getRestTimeInNanos(value);
                }
            });
            cache = builder.build();
            // 因为调用关系比较简单,使用了匿名内部类
            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) {
                    return cache.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;
                }
            };
        }
    }
    

    3. 总结

    1. 在使用模板方法设计模式时,可以将所有待实现模板方法内聚到一个类中,即内聚又相对独立,再加上可注入的工厂类,可进一步提高扩展性。

    2. 依赖注入随处可见,是提供高扩展性的必备模式。

    3. 既要面向接口编程,又要避免强转,实现方式值得思考。

    4. Builder模式相比工厂模式创建实例,也很常见,属于必会设计模式。

    end.


    <–阅过留痕,左边点赞!


    https://juejin.im/post/5ed3c0c96fb9a047dc2cc38e

    「点点赞赏,手留余香」

      还没有人赞赏,快来当第一个赞赏的人吧!
    0 条回复 A 作者 M 管理员
      所有的伟大,都源于一个勇敢的开始!
    欢迎您,新朋友,感谢参与互动!欢迎您 {{author}},您在本站有{{commentsCount}}条评论