基于MyBatis二级缓存深入装饰器模式

news/2024/7/5 3:08:39

视频地址

学习文档


文章目录

  • 一、示意代码
  • 二、装饰器
  • 三、经典案例—MyBatis二级缓存
    • 1、Cache 标准定义
    • 2、PerpetualCache 基础实现
    • 3、增强实现
      • 3-1、ScheduledCache
      • 3-2、LruCache


先来说说我对装饰器理解:当你有一个基础功能的代码,但你想在不改变原来代码的基础上进行功能增强,并且可以随意组合增强的时候,就可以使用装饰器模式。

我把装饰器模式理解成是一种套娃模式,所谓套娃就是一层套一层,随意组合想怎么套就怎么套,层层嵌套的基础是有一个最初的原型。


一、示意代码


对于个人而言,首先是一个人,其次可以有不同的身份,各种身份可以随意组合。这就可以看成一个装饰器模式的案例。

// 定义接口
interface Human {
    String identity();
}

// 基础实现
class OrdinaryHuman implements Human {
    @Override
    public String identity() {
        return "我是一个人";
    }
}


// 增强实现
class Teacher implements Human {
    protected Human human;

    public teacher(Human human) {
        this.human = human;
    }

    @Override
    public String identity() {
        human.identity();
        return "我是一名老师";
    }
}

// 增强实现
class Father implements Human {
    protected Human human;

    public father(Human human) {
        this.human = human;
    }

    @Override
    public String identity() {
        human.identity();
        return "我是一名父亲";
    }
}
public static void main(String[] args) {
    OrdinaryHuman ordinaryHuman = new OrdinaryHuman();
    Teacher teacher = new Teacher(ordinaryHuman);
    Father father = new Father(teacher);

    father.identity();
}

输出

我是一个人
我是一名老师
我是一名父亲

二、装饰器


GPT的解释
装饰器模式是一种能够在不改变原对象代码的情况下,动态地为对象添加新功能的设计模式。通过将对象包装在装饰器类中,可以透明地、在运行时选择性地、以任意顺序地应用这些功能。最终效果是通过组合不同的装饰器,扩展原对象功能,使系统更灵活可扩展。


想要实现一个装饰器,如下三步:

  1. 要有一个标准定义(Human)
  2. 要有一个基础的实现(OrdinaryHuman)
  3. 每个增强的的实现除了实现标准定义重写对应的方法之外,还要提供一个特殊的构造方法,参数就是接口的某个实现类(基于这个特殊的构造方法,就可以随意组合了)

三、经典案例—MyBatis二级缓存


在这里插入图片描述


  1. Cache 标准定义
  2. PerpetualCache 基础实现
  3. decorators 包下面的就是各种增强实现

注:在现在分布式系统的大环境下,MyBatis的二级缓存几乎没有用武之地,但它确实理解装饰器模式的一个好案例,它的代码很简单,所以格外的好理解


1、Cache 标准定义


cache里面定义了缓存的一些操作

public interface Cache {
    String getId();

    void putObject(Object var1, Object var2);

    Object getObject(Object var1);

    Object removeObject(Object var1);

    void clear();

    int getSize();

    default ReadWriteLock getReadWriteLock() {
        return null;
    }
}

2、PerpetualCache 基础实现


PerpetualCache 的实现也很简单,就是一个 HashMap,Value就是缓存的数据,Key 是自定义的 CacheKey

public class PerpetualCache implements Cache {
    private final String id;
    private Map<Object, Object> cache = new HashMap();

    public PerpetualCache(String id) {
        this.id = id;
    }

    public String getId() {
        return this.id;
    }

    public int getSize() {
        return this.cache.size();
    }

    public void putObject(Object key, Object value) {
        this.cache.put(key, value);
    }

    public Object getObject(Object key) {
        return this.cache.get(key);
    }

    public Object removeObject(Object key) {
        return this.cache.remove(key);
    }

    public void clear() {
        this.cache.clear();
    }

    public boolean equals(Object o) {
        if (this.getId() == null) {
            throw new CacheException("Cache instances require an ID.");
        } else if (this == o) {
            return true;
        } else if (!(o instanceof Cache)) {
            return false;
        } else {
            Cache otherCache = (Cache)o;
            return this.getId().equals(otherCache.getId());
        }
    }

    public int hashCode() {
        if (this.getId() == null) {
            throw new CacheException("Cache instances require an ID.");
        } else {
            return this.getId().hashCode();
        }
    }
}

CacheKey 并不是装饰器的里面内容,但它的设计挺有意思的,简单来说就是:把当前这次操作的标识,比如 接口全限定名、请求参数、执行器信息 存入 List里面, 基于这些信息去生成唯一的 HashCode

public class CacheKey implements Cloneable, Serializable {
    private static final long serialVersionUID = 1146682552656046210L;
    public static final CacheKey NULL_CACHE_KEY = new CacheKey() {
        public void update(Object object) {
            throw new CacheException("Not allowed to update a null cache key instance.");
        }

        public void updateAll(Object[] objects) {
            throw new CacheException("Not allowed to update a null cache key instance.");
        }
    };
    private static final int DEFAULT_MULTIPLIER = 37;
    private static final int DEFAULT_HASHCODE = 17;
    private final int multiplier;
    private int hashcode;
    private long checksum;
    private int count;
    private List<Object> updateList;

    public CacheKey() {
        this.hashcode = 17;
        this.multiplier = 37;
        this.count = 0;
        this.updateList = new ArrayList();
    }

    public CacheKey(Object[] objects) {
        this();
        this.updateAll(objects);
    }

    public int getUpdateCount() {
        return this.updateList.size();
    }

    public void update(Object object) {
        int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object);
        ++this.count;
        this.checksum += (long)baseHashCode;
        baseHashCode *= this.count;
        this.hashcode = this.multiplier * this.hashcode + baseHashCode;
        this.updateList.add(object);
    }

    public void updateAll(Object[] objects) {
        Object[] var2 = objects;
        int var3 = objects.length;

        for(int var4 = 0; var4 < var3; ++var4) {
            Object o = var2[var4];
            this.update(o);
        }

    }

    public boolean equals(Object object) {
        if (this == object) {
            return true;
        } else if (!(object instanceof CacheKey)) {
            return false;
        } else {
            CacheKey cacheKey = (CacheKey)object;
            if (this.hashcode != cacheKey.hashcode) {
                return false;
            } else if (this.checksum != cacheKey.checksum) {
                return false;
            } else if (this.count != cacheKey.count) {
                return false;
            } else {
                for(int i = 0; i < this.updateList.size(); ++i) {
                    Object thisObject = this.updateList.get(i);
                    Object thatObject = cacheKey.updateList.get(i);
                    if (!ArrayUtil.equals(thisObject, thatObject)) {
                        return false;
                    }
                }

                return true;
            }
        }
    }

    public int hashCode() {
        return this.hashcode;
    }

    public String toString() {
        StringJoiner returnValue = new StringJoiner(":");
        returnValue.add(String.valueOf(this.hashcode));
        returnValue.add(String.valueOf(this.checksum));
        Stream var10000 = this.updateList.stream().map(ArrayUtil::toString);
        Objects.requireNonNull(returnValue);
        var10000.forEach(returnValue::add);
        return returnValue.toString();
    }

    public CacheKey clone() throws CloneNotSupportedException {
        CacheKey clonedCacheKey = (CacheKey)super.clone();
        clonedCacheKey.updateList = new ArrayList(this.updateList);
        return clonedCacheKey;
    }
}

3、增强实现


它这个里面的增强实现挺多的,着重看两个有意思的 ScheduledCache、LruCache


3-1、ScheduledCache


ScheduledCache 是一个支持定时清除缓存数据,可以通过 setClearInterval 方法设置清除时间缓存间隔,单位毫秒。

public void setClearInterval(long clearInterval) {
    this.clearInterval = clearInterval;
}


清除缓存的原理并没有很复杂,它的代码超级简单, this.lastClear 是上一次清除的时间,只需要在操作缓存的之前先调用下面的代码即可。

private boolean clearWhenStale() {
    if (System.currentTimeMillis() - this.lastClear > this.clearInterval) {
        this.clear();
        return true;
    } else {
        return false;
    }
}

全部代码如下:

public class ScheduledCache implements Cache {
    private final Cache delegate;
    protected long clearInterval;
    protected long lastClear;

    public ScheduledCache(Cache delegate) {
        this.delegate = delegate;
        this.clearInterval = TimeUnit.HOURS.toMillis(1L);
        this.lastClear = System.currentTimeMillis();
    }

    public void setClearInterval(long clearInterval) {
        this.clearInterval = clearInterval;
    }

    public String getId() {
        return this.delegate.getId();
    }

    public int getSize() {
        this.clearWhenStale();
        return this.delegate.getSize();
    }

    public void putObject(Object key, Object object) {
        this.clearWhenStale();
        this.delegate.putObject(key, object);
    }

    public Object getObject(Object key) {
        return this.clearWhenStale() ? null : this.delegate.getObject(key);
    }

    public Object removeObject(Object key) {
        this.clearWhenStale();
        return this.delegate.removeObject(key);
    }

    public void clear() {
        this.lastClear = System.currentTimeMillis();
        this.delegate.clear();
    }

    public int hashCode() {
        return this.delegate.hashCode();
    }

    public boolean equals(Object obj) {
        return this.delegate.equals(obj);
    }

    private boolean clearWhenStale() {
        if (System.currentTimeMillis() - this.lastClear > this.clearInterval) {
            this.clear();
            return true;
        } else {
            return false;
        }
    }
}

3-2、LruCache


GPT对LRU的解释
LRU(Least Recently Used)是一种缓存淘汰策略,用于管理缓存中的数据项。该策略的基本思想是,当缓存达到容量上限时,选择最近最少被使用的数据项进行淘汰。


LruCache 的代码很少,来一起看看它是如何实现LUR的

  1. eldestKey 最少被使用的一个数据,当超过容量的时候就删除它
  2. 每次添加元素的时候都可能超容,通过 cycleKeyList 方法判断是否需要删除数据
  3. LinkedHashMap 提供了一个删除最后一个元素的接口 removeEldestEntry ,通过它来删除最后一个元素
public class LruCache implements Cache {
    private final Cache delegate;
    private Map<Object, Object> keyMap;
    private Object eldestKey;

    public LruCache(Cache delegate) {
        this.delegate = delegate;
        this.setSize(1024);
    }

    public String getId() {
        return this.delegate.getId();
    }

    public int getSize() {
        return this.delegate.getSize();
    }

    public void setSize(final int size) {
        this.keyMap = new LinkedHashMap<Object, Object>(size, 0.75F, true) {
            private static final long serialVersionUID = 4267176411845948333L;
            
            // eldest 就是链表中最后一个元素
            // 当容量大小超过的时候就让 LruCache.this.eldestKey = eldest.getKey();
            protected boolean removeEldestEntry(Entry<Object, Object> eldest) {
                boolean tooBig = this.size() > size;
                if (tooBig) {
                    LruCache.this.eldestKey = eldest.getKey();
                }

                return tooBig;
            }
        };
    }

    public void putObject(Object key, Object value) {
        this.delegate.putObject(key, value);
        this.cycleKeyList(key);
    }

    public Object getObject(Object key) {
        this.keyMap.get(key);
        return this.delegate.getObject(key);
    }

    public Object removeObject(Object key) {
        return this.delegate.removeObject(key);
    }

    public void clear() {
        this.delegate.clear();
        this.keyMap.clear();
    }

    private void cycleKeyList(Object key) {
        this.keyMap.put(key, key);
        // 如果当前要删除的元素不为空,就删除这个元素
        if (this.eldestKey != null) {
            this.delegate.removeObject(this.eldestKey);
            this.eldestKey = null;
        }

    }
}

基于上面的分析,只需要来看看 removeEldestEntry 方法是在什么时候调用的就好了,往LinkedHashMap添加元素的时候,调用的实际上是 HashMap 的 put方法

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

省去插入元素和扩容的代码,调用了 afterNodeInsertion方法 evict = true

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    
    // ....
    afterNodeInsertion(evict);
    return null;
}

LinkedHashMap中 afterNodeInsertion 实现,把链表头也是最开始的元素传递给 removeEldestEntry

void afterNodeInsertion(boolean evict) { // possibly remove eldest
    LinkedHashMap.Entry<K,V> first;
    if (evict && (first = head) != null && removeEldestEntry(first)) {
        K key = first.key;
        removeNode(hash(key), key, null, false, true);
    }
}

http://lihuaxi.xjx100.cn/news/1883027.html

相关文章

大模型的可解释性

摘要&#xff1a; 可解释性为基础大模型提供了清晰的认识:支撑基础大模型的深度神经网络的不透明性&#xff0c;以及基础大模型的预期普遍性&#xff0c;提高了对这些模型及其能力的理解需求。目前的可解释性方法一般是为演绎和解释特定任务模型的行为而设计的;基础大模型的性质…

最简单的pixel刷机和安装面具、lsposed

一 下载手机对应的系统 1&#xff0c;手机usb连接然后重启进入Fastboot模式&#xff1a;adb reboot bootloader2&#xff0c;找到你下载的系统&#xff0c;Windows 系统 直接运行 flash-all.bat上图 &#xff1a;左边就是安卓11和12的系统&#xff0c;右边是对应的手机型号 下…

统信UOS_麒麟KYLINOS上使用命令行配置NTP服务器

原文链接&#xff1a;统信UOS/麒麟KYLINOS上使用命令行配置NTP hello&#xff0c;大家好啊&#xff0c;今天我要给大家介绍的是在统信UOS/麒麟KYLINOS操作系统上使用命令行配置NTP&#xff08;Network Time Protocol&#xff09;服务器的方法。在内网环境下&#xff0c;许多企业…

A* 算法简介

一、A* 算法简介A* algorithm is a popular choice for graph search. Breadth First Search is the simplest of the graph search algorithms. Graph search algorithms, including A*, take a “graph” as input. A* algorithm is a modification of Dijkstra’s Algorithm…

error:gmapping

– Could not find the required component ‘gmapping’. The following CMake error indicates that you either need to install the package with the same name or change your environment so that it can be found. CMake Error at /opt/ros/kinetic/share/catkin/cmake…

通过虚拟机安装Open5GS 和UERANSIM记录

目录 wsl虚拟环境尝试失败 step1 安装wsl: step2下载Ubuntu 20.04.6 LTS: step3升级wsl&#xff1a; step4生成用户: step5 linux下安装软件需要的镜像&#xff1a; step6 安装图形界面xfce和浏览器&#xff1a; step6 安装chrome virtual box安装ubuntu step7&#xf…

HarmonyOS鸿蒙操作系统架构开发

什么是HarmonyOS鸿蒙操作系统&#xff1f; HarmonyOS是华为公司开发的一种全场景分布式操作系统。它可以在各种智能设备&#xff08;如手机、电视、汽车、智能穿戴设备等&#xff09;上运行&#xff0c;具有高效、安全、低延迟等优势。 目录 HarmonyOS 一、HarmonyOS 与其他操…

AI 绘画 | Stable Diffusion 黑白老照片上色修复

前言 Stable Diffusion WEB UI 依靠各种第三方插件,可是实现很多功能,今天主要讲解如何使用ControNet插件的Recolor (重上色) 实现黑白老照片的上色和修复,变成一个彩色高清的图片。 ControNet 官方地址: https://github.com/lllyasviel/ControlNet 教程 安装ControNet…