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
对象,最后通过configuration
、Executor
、autoCommit
参数构建了SqlSession
。
3. 代理模式的应用
代理模式是 Mybatis 核心使用的模式,使我们只需要编写Mapper.java
接口,不需要实现,由 Mybatis 背后完成具体 SQL 的执行。
代理模式(Proxy Pattern):给某个对象提供一个代理,并由代理对象控制对原对象的引用。
示例图
每次调用sqlSession
的getMapper
方法时,都会创建一个新的动态代理类实例。
示例图
当我们使用Configuration
的getMapper
方法时,会调用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
的标准装饰器有:
FifoCache
LoggingCache
LruCache
ScheduledCache
SerializedCache
SoftCache
SynchronizedCache
WeakCache
Mybatis 采用装饰器模式实现缓存功能,通过组合而非继承,更加灵活,避免了继承关系的组合爆炸。
6. 迭代器模式的应用
迭代器模式介绍
- 迭代器模式是一个行为型设计模式,用于在不暴露其底层表示的情况下顺序访问集合对象的元素。在大多数编程语言中,迭代器已经成为基础的类库,直接用来遍历集合对象。在日常开发中,我们通常直接使用现有的迭代器,而不需要从零实现一个。
- 在软件系统中,容器对象有两个职责:存储数据和遍历数据。从依赖性角度看,前者是聚合对象的基本职责,后者是可变化且可分离的。因此,可以将遍历数据的行为从容器中抽取出来,封装到迭代器对象中,由迭代器提供遍历数据的功能。这将简化聚合对象的设计,更加符合单一职责原则。
迭代器模式主要包含以下角色:
- 抽象集合(Aggregate)角色:用于存储和管理元素对象,定义存储、添加、删除集合元素的功能,并声明一个
createIterator()
方法用于创建迭代器对象。 - 具体集合(ConcreteAggregate)角色:实现抽象集合类,返回一个具体迭代器的实例。
- 抽象迭代器(Iterator)角色:定义访问和遍历聚合元素的接口,通常包含
hasNext()
、next()
等方法。hasNext()
方法用于判断集合中是否还有下一个元素。next()
方法用于将游标后移一位元素。currentItem()
方法用于返回当前游标指向的元素。
- 具体迭代器(ConcreteIterator)角色:实现抽象迭代器接口中定义的方法,完成对集合对象的遍历,同时记录遍历的当前位置。
在Java中,Iterator
接口就是迭代器模式的实现,只要实现了该接口,就相当于应用了迭代器模式:
迭代器模式总结
使用场景
- 访问一个聚合对象的内容,不需要暴露它的内部表示。
- 支持对聚合对象的多种遍历。
- 迭代器模式与集合同时存在。
优点
- 支持以不同的方式遍历一个聚合对象,在同一个聚合对象上可以定义多种遍历方式。
- 迭代器简化了聚合类。引入迭代器模式后,聚合对象不再需要自行提供数据遍历访问的方法。
- 可以为不同的聚合结构提供一个统一的接口。
缺点
- 迭代器模式将存储数据和遍历数据的职责分离开,增加新的聚合类型需要增加对应的新迭代器类,增加了系统复杂性。
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;}@Overridepublic boolean hasNext() {return children != null;}@Overridepublic PropertyTokenizer next() {return new PropertyTokenizer(children);}@Overridepublic void remove() {throw new UnsupportedOperationException("Remove is not supported, as it has no meaning in the context of properties.");}
}
这个类传入一个字符串到构造函数,然后提供了 iterator 方法对解析后的子串进行遍历,是一个非常常用的方法类。
PropertyTokenizer
类虽然实现了 Iterator
接口,但并非标准的迭代器类。它将配置解析、解析后的元素、迭代器这三部分本应分开的代码耦合在一起,因此略显复杂。不过,这样做的好处是能够实现惰性解析,不需要事先将整个配置解析成多个 PropertyTokenizer
对象,只有在调用 next()
方法时才会解析部分配置。