MyBatis源码中的设计模式1

1. 建造者模式的应用

建造者模式属于创建类模式,通过一步一步地创建一个复杂的对象,能够将部件与其组装过程分开。用户只需指定复杂对象的类型,就可以得到该对象,而不需要了解其内部的具体构造细节。《Effective Java》中也提到,遇到多个构造器参数时,考虑用构建者(Builder)模式。

在 Mybatis 的环境初始化过程中,SqlSessionFactoryBuilder会调用XMLConfigBuilder读取所有的MybatisMapConfig.xml和所有的*Mapper.xml文件,构建 Mybatis 运行的核心对象Configuration对象,然后将该Configuration对象作为参数构建一个SqlSessionFactory对象。

示例图

在这里插入图片描述

其中,XMLConfigBuilder在构建Configuration对象时,也会调用XMLMapperBuilder用于读取*.Mapper文件,而XMLMapperBuilder会使用XMLStatementBuilder来读取和构建所有的 SQL 语句。

示例图

在这里插入图片描述

在这个过程中,Builder模式会读取文件或者配置,然后做大量的 XPath 解析、配置或语法解析、反射生成对象、存入结果缓存等步骤。因此,大量采用了 Builder 模式来解决这些问题。

对于Builder的具体类,方法大都用build*开头,比如SqlSessionFactoryBuilder类中包含的方法:

示例图

在这里插入图片描述

从建造者模式的设计初衷来看,SqlSessionFactoryBuilder虽然带有 Builder 后缀,但不完全是标准的建造者模式。它的设计初衷是为了简化开发,隐藏构建SqlSessionFactory的复杂过程,对程序员透明。

2. 工厂模式的应用

在 Mybatis 中,SqlSessionFactory使用了简单工厂模式。

简单工厂模式(Simple Factory Pattern):又称为静态工厂方法(Static Factory Method)模式,它属于类创建型模式。简单工厂模式中,可以根据参数的不同返回不同类的实例。

示例图

在这里插入图片描述

SqlSession是 Mybatis 工作的核心接口,通过这个接口可以执行 SQL 语句、获取 Mappers、管理事务。

示例图

在这里插入图片描述

DefaultSqlSessionFactory的默认工厂实现里,openSessionFromDataSource方法展示了工厂如何产出一个产品:

示例图

在这里插入图片描述

这个方法会先从configuration读取对应的环境配置,然后初始化TransactionFactory获得一个Transaction对象,通过Transaction获取一个Executor对象,最后通过configurationExecutorautoCommit参数构建了SqlSession

3. 代理模式的应用

代理模式是 Mybatis 核心使用的模式,使我们只需要编写Mapper.java接口,不需要实现,由 Mybatis 背后完成具体 SQL 的执行。

代理模式(Proxy Pattern):给某个对象提供一个代理,并由代理对象控制对原对象的引用。

示例图

在这里插入图片描述

每次调用sqlSessiongetMapper方法时,都会创建一个新的动态代理类实例。

示例图

在这里插入图片描述

当我们使用ConfigurationgetMapper方法时,会调用mapperRegistry.getMapper方法,

示例图

在这里插入图片描述

在这里,通过T newInstance(SqlSession sqlSession)方法得到一个MapperProxy对象,然后调用T newInstance(MapperProxy<T> mapperProxy)生成代理对象。

示例图

在这里插入图片描述

通过这种方式,我们只需要编写Mapper.java接口类,实际执行时会转发给MapperProxy.invoke方法,调用后续的sqlSession.cud > executor.execute > prepareStatement等方法,完成 SQL 的执行和返回。

4. 模板方法模式的应用

在 Mybatis 中,sqlSession的 SQL 执行委托给Executor实现,Executor包含以下结构:

示例图

在这里插入图片描述

其中的BaseExecutor采用了模板方法模式,实现了大部分的 SQL 执行逻辑,把几个方法交给子类定制化完成。

示例图

在这里插入图片描述

模板模式基于继承实现代码复用。抽象类中包含模板方法,调用有待子类实现的抽象方法。

5. 装饰者模式的应用

装饰模式(Decorator Pattern):动态地给一个对象增加一些额外的职责,比生成子类实现更为灵活。

在 Mybatis 中,缓存功能由根接口Cache定义,采用装饰器设计模式,数据存储和缓存的基本功能由PerpetualCache实现,然后通过一系列的装饰器来对PerpetualCache进行缓存策略等方面的控制。

示例图

在这里插入图片描述

用于装饰PerpetualCache的标准装饰器有:

  1. FifoCache
  2. LoggingCache
  3. LruCache
  4. ScheduledCache
  5. SerializedCache
  6. SoftCache
  7. SynchronizedCache
  8. WeakCache

Mybatis 采用装饰器模式实现缓存功能,通过组合而非继承,更加灵活,避免了继承关系的组合爆炸。

6. 迭代器模式的应用

迭代器模式介绍

  • 迭代器模式是一个行为型设计模式,用于在不暴露其底层表示的情况下顺序访问集合对象的元素。在大多数编程语言中,迭代器已经成为基础的类库,直接用来遍历集合对象。在日常开发中,我们通常直接使用现有的迭代器,而不需要从零实现一个。
  • 在软件系统中,容器对象有两个职责:存储数据和遍历数据。从依赖性角度看,前者是聚合对象的基本职责,后者是可变化且可分离的。因此,可以将遍历数据的行为从容器中抽取出来,封装到迭代器对象中,由迭代器提供遍历数据的功能。这将简化聚合对象的设计,更加符合单一职责原则。

在这里插入图片描述

迭代器模式主要包含以下角色:

  1. 抽象集合(Aggregate)角色:用于存储和管理元素对象,定义存储、添加、删除集合元素的功能,并声明一个 createIterator() 方法用于创建迭代器对象。
  2. 具体集合(ConcreteAggregate)角色:实现抽象集合类,返回一个具体迭代器的实例。
  3. 抽象迭代器(Iterator)角色:定义访问和遍历聚合元素的接口,通常包含 hasNext()next() 等方法。
    • hasNext() 方法用于判断集合中是否还有下一个元素。
    • next() 方法用于将游标后移一位元素。
    • currentItem() 方法用于返回当前游标指向的元素。
  4. 具体迭代器(ConcreteIterator)角色:实现抽象迭代器接口中定义的方法,完成对集合对象的遍历,同时记录遍历的当前位置。

在Java中,Iterator接口就是迭代器模式的实现,只要实现了该接口,就相当于应用了迭代器模式:

在这里插入图片描述

迭代器模式总结

使用场景

  1. 访问一个聚合对象的内容,不需要暴露它的内部表示。
  2. 支持对聚合对象的多种遍历。
  3. 迭代器模式与集合同时存在。

优点

  1. 支持以不同的方式遍历一个聚合对象,在同一个聚合对象上可以定义多种遍历方式。
  2. 迭代器简化了聚合类。引入迭代器模式后,聚合对象不再需要自行提供数据遍历访问的方法。
  3. 可以为不同的聚合结构提供一个统一的接口。

缺点

  1. 迭代器模式将存储数据和遍历数据的职责分离开,增加新的聚合类型需要增加对应的新迭代器类,增加了系统复杂性。

MyBatis中的应用

MyBatis的 PropertyTokenizer 是 property 包中的重要类,它实现了 Iterator 接口,并在 reflection 包中的其他类中被频繁引用。该类的 hasNext() 方法经常被使用。

/**
 * 属性分词器
 * 
 * 实现 Iterator 接口,用于遍历属性的各个部分
 */
public class PropertyTokenizer implements Iterator<PropertyTokenizer> {

    private String name;
    private final String indexedName;
    private String index;
    private final String children;

    public PropertyTokenizer(String fullname) {
        int delim = fullname.indexOf('.');
        if (delim > -1) {
            name = fullname.substring(0, delim);
            children = fullname.substring(delim + 1);
        } else {
            name = fullname;
            children = null;
        }
        indexedName = name;
        delim = name.indexOf('[');
        if (delim > -1) {
            index = name.substring(delim + 1, name.length() - 1);
            name = name.substring(0, delim);
        }
    }

    public String getName() {
        return name;
    }

    public String getIndex() {
        return index;
    }

    public String getIndexedName() {
        return indexedName;
    }

    public String getChildren() {
        return children;
    }

    @Override
    public boolean hasNext() {
        return children != null;
    }

    @Override
    public PropertyTokenizer next() {
        return new PropertyTokenizer(children);
    }

    @Override
    public void remove() {
        throw new UnsupportedOperationException("Remove is not supported, as it has no meaning in the context of properties.");
    }
}

这个类传入一个字符串到构造函数,然后提供了 iterator 方法对解析后的子串进行遍历,是一个非常常用的方法类。

PropertyTokenizer 类虽然实现了 Iterator 接口,但并非标准的迭代器类。它将配置解析、解析后的元素、迭代器这三部分本应分开的代码耦合在一起,因此略显复杂。不过,这样做的好处是能够实现惰性解析,不需要事先将整个配置解析成多个 PropertyTokenizer 对象,只有在调用 next() 方法时才会解析部分配置。

相关推荐

  1. 详解Redis设计模式设计思想

    2024-07-17 12:20:02       20 阅读
  2. Spring经典7种设计模式分析

    2024-07-17 12:20:02       31 阅读
  3. MyBatis框架5种设计模式总结

    2024-07-17 12:20:02       56 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-17 12:20:02       67 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-17 12:20:02       72 阅读
  3. 在Django里面运行非项目文件

    2024-07-17 12:20:02       58 阅读
  4. Python语言-面向对象

    2024-07-17 12:20:02       69 阅读

热门阅读

  1. android.app.application can not be cast to android.app.Activity

    2024-07-17 12:20:02       21 阅读
  2. 优化Conda环境:深入掌握conda clean命令的清理艺术

    2024-07-17 12:20:02       22 阅读
  3. 探索Conda的搜索能力:挖掘Python包的宝藏

    2024-07-17 12:20:02       28 阅读
  4. conda 环境打包与使用

    2024-07-17 12:20:02       29 阅读
  5. C语言——练习:将数组中的n个元素按逆序存放

    2024-07-17 12:20:02       23 阅读
  6. django form 将表单数据发送到后端触发弹窗

    2024-07-17 12:20:02       31 阅读
  7. 什么样的服务器是合乎直销网站标准

    2024-07-17 12:20:02       19 阅读
  8. FinClip 中如何使用小程序插件?

    2024-07-17 12:20:02       25 阅读
  9. Fastgpt本地或服务器私有化部署常见问题

    2024-07-17 12:20:02       25 阅读
  10. 跟ChatGPT学习go语言-float64转成int

    2024-07-17 12:20:02       23 阅读
  11. Redis--布隆过滤器

    2024-07-17 12:20:02       22 阅读
  12. geometry_msgs

    2024-07-17 12:20:02       23 阅读