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

视频地址

学习文档


文章目录

  • 一、示意代码
  • 二、装饰器
  • 三、经典案例—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);
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/235546.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

案例027:基于微信小程序的校园二手平台的设计与实现

文末获取源码 开发语言&#xff1a;Java 框架&#xff1a;SSM JDK版本&#xff1a;JDK1.8 数据库&#xff1a;mysql 5.7 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.5.4 小程序框架&#xff1a;uniapp 小程序开发软件&#xff1a;HBuilder X 小程序…

【Docker】进阶之路:(二)Docker简介

【Docker】进阶之路&#xff1a;&#xff08;二&#xff09;Docker简介 什么是 DockerDocker 由来与发展历程Docker的架构与组成Docker容器生态容器核心技术容器规范容器平台技术 为什么使用DockerDocker的应用场景 什么是 Docker 简单地讲&#xff0c;Docker就是一个应用容器…

面向对象类的设计和实现

实验目标 本实验任务是实现 Java 类的设计和实现&#xff0c;实验任务是根据每年新生的报到流程&#xff0c; 设计一 个学生管理系统&#xff0c;实现学生的注册和报到功能。设置类的基本属性&#xff0c;实现 getter 和 setter 方 法&#xff0c;通过 set 方法设置…

C++新经典模板与泛型编程:SFINAE特性的信息萃取

用成员函数重载实现is_default_constructible 首先介绍一个C标准库提供的可变参类模板std::is_default_constructible。这个类模板的主要功能是判断一个类的对象是否能被默认构造&#xff08;所谓默认构造&#xff0c;就是构造一个类对象时&#xff0c;不需要给该类的构造函数…

三层交换原理

三层交换机出现的背景 早期的网络中一般使用二层交换机来搭建局域网&#xff0c;而不同局域网之间的网络互通由路由器来完成。那时的网络流量&#xff0c;局域网内部的流量占了绝大部分&#xff0c;而网络间的通信访问量比较少&#xff0c;使用少量路由器已经足够应付了。 但…

六级高频词汇3

目录 单词 参考链接 单词 400. nonsense n. 胡说&#xff0c;冒失的行动 401. nuclear a. 核子的&#xff0c;核能的 402. nucleus n. 核 403. retail n. /v. /ad. 零售 404. retain vt. 保留&#xff0c;保持 405. restrict vt. 限制&#xff0c;约束 406. sponsor n. …

GRE与顺丰圆通快递盒子

1. DNS污染 随想&#xff1a; 在输入一串网址后&#xff0c;会发生如下变化如果你在系统中配置了 Hosts 文件&#xff0c;那么电脑会先查询 Hosts 文件如果 Hosts 里面没有这个别名&#xff0c;就通过域名服务器查询域名服务器回应了&#xff0c;那么你的电脑就可以根据域名服…

使用阿里巴巴同步工具DataX实现Mysql与ElasticSearch数据同步

一、Linux环境要求 二、准备工作 2.1 Linux安装jdk 2.2 linux安装python 2.3 下载DataX&#xff1a; 三、DataX压缩包导入&#xff0c;解压缩 四、编写同步Job 五、执行Job 六、定时更新 6.1 创建定时任务 6.2 提交定时任务 6.3 查看定时任务 七、增量更新思路 一、Linux环境要…

KubeSphere应用【二】Docker安装

一、Docker安装 1.下载Docker安装包 【地址】Index of linux/static/stable/x86_64/ 2.上传至服务器 # 解压文件 tar -xvf docker-20.10.10.tgz# 将docker 目录中的所有文件复制至/usr/bin/目录下 cp docker/* /usr/bin 3.配置docker.service文件 vim /usr/lib/systemd/sy…

WPF仿网易云搭建笔记(2):组件化开发

文章目录 前言专栏和Gitee仓库依赖属性实战&#xff1a;缩小&#xff0c;全屏&#xff0c;关闭按钮依赖属性操作封装主窗口传递this本身给TitleView标题控件主要代码MainWindow.xmalMainWindow.cs依赖属性方法封装TitleView.csTitleViewModelTitleViewModel实现效果 前言 这次…

【JavaWeb学习专栏 | CSS篇】css简单介绍 css常用选择器集锦

个人主页&#xff1a;[兜里有颗棉花糖(https://xiaofeizhu.blog.csdn.net/) 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 兜里有颗棉花糖 原创 收录于专栏【JavaWeb学习专栏】【Java系列】 希望本文内容可以帮助到大家&#xff0c;一起加油吧&#xff01;…

安全访问服务边缘(SASE):解决第三方风险的全方位解决方案

随着数字化时代的到来&#xff0c;企业和组织对于网络安全的需求越来越迫切。传统的安全解决方案已经无法满足复杂多变的网络环境&#xff0c;因此新兴的安全访问服务边缘&#xff08;SASE&#xff09;应运而生。本文将介绍SASE的概念和工作原理&#xff0c;并重点阐述它作为第…

第二百回 如何获取App自身的信息

文章目录 1. 概念介绍2. 使用方法2.1 ClipOval2.2 ClipRRect 3. 示例代码 我们在上一章回中介绍了AspectRatio Widget相关的内容&#xff0c;本章回中将介绍剪裁类组件(Clip).闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. 概念介绍 我们在这里说的剪裁类组件主要是指对…

Koa2从零搭建restful API

Koa2从零搭建restful API: 创建项目文件夹并进入 mkdir koa-projectcd koa-project 初始化项目 npm init 安装 Koa npm install koa koa-router --save 编写示例代码&#xff0c;在 app.js 文件中编写以下代码&#xff1a; // koa项目的入口文件 const Koa require("koa…

018 OpenCV 人脸检测

目录 一、环境 二、分类器原理 2.1、概述 2.2、工作原理 三、人脸检测代码 一、环境 本文使用环境为&#xff1a; Windows10Python 3.9.17opencv-python 4.8.0.74 二、分类器原理 CascadeClassifier是OpenCV&#xff08;开源计算机视觉库&#xff09;中的一个强大的类…

想进阶JAVA高级程序员吗?多线程必学

❤️作者主页&#xff1a;小虚竹 ❤️作者简介&#xff1a;大家好,我是小虚竹。2022年度博客之星评选TOP 10&#x1f3c6;&#xff0c;Java领域优质创作者&#x1f3c6;&#xff0c;CSDN博客专家&#x1f3c6;&#xff0c;华为云享专家&#x1f3c6;&#xff0c;掘金年度人气作…

Truffle的基础语法与js测试语法

truffle编译 truffle compiletruffle部署 truffle migratetruffle测试 使用test文件夹下的所有文件测试 truffle test使用单个文件 测试 truffle test 文件所在位置assert断言 assert.equal 是一种常见的断言函数&#xff0c;用于测试两个值是否相等。它接受两个参数&…

代码随想录算法训练营第四十六天 _ 动态规划_背包问题总结。

学习目标&#xff1a; 动态规划五部曲&#xff1a; ① 确定dp[i]的含义 ② 求递推公式 ③ dp数组如何初始化 ④ 确定遍历顺序 ⑤ 打印递归数组 ---- 调试 引用自代码随想录&#xff01; 本文大多数内容引用自代码随想录 60天训练营打卡计划&#xff01; 学习内容&#xff1a; …

银行插件导致的Outlook客户端无法连接服务器问题

问题现象 最近遇到好些同事出现outlook客户端无法连接服务器的情况&#xff0c;具体现象就是右下角一直显示【正在尝试连接…】或者【需要密码】&#xff0c;点击【需要密码】按钮&#xff0c;输密码的弹窗是一个完全空白的页面。 此时打开word&#xff0c;右上角那里去登录o…

Postman获取token

问题描述 登录接口中带有token参数&#xff0c;其他接口需要带上token才能正确访问&#xff0c;利用接口查询用户信息时手动在headers中更新token信息并不方便。 解决方案 在登录接口中设置一个名为“token”的环境变量&#xff0c;value为登录接口跑通之后responseBody中返回…