都知道开源框架的优秀,有一部分是使用了设计模式, 今天我们来学习一下在mybatis的初始化中是如何使用建造者模式创建对象的.
首先,我们来复习一下建造者模式;
Builder 建造者模式(创建型对象类型):将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示;
典型的KFC 儿童套餐:主食、辅食、饮料、玩具。这些在不同的儿童餐中可以是不同的,但是组合成儿童餐的过程是相同的。
建造者模式的类图为:
对应的代码:
/**
* @ClassName: KFCChildrenMeal
* @Description: KFC 儿童餐实体
* @Author: 唐欢
* @Date: 2022/6/20 10:25
* @Version 1.0
*/
public class KFCChildrenMeal {
private String stapleFood;
private String complementaryFood;
private String toy;
private String drink;
//get和set 省略
.....
@Override
public String toString() {
return "KFCChildrenMeal{" +
"stapleFood='" + stapleFood + '\'' +
", complementaryFood='" + complementaryFood + '\'' +
", toy='" + toy + '\'' +
", drink='" + drink + '\'' +
'}';
}
}
建造者(builder): 为了创建一个Product
对象的各个部件指定的抽象接口.
/**
* @ClassName: Builder
* @Description:抽象建造者/建造者接口(Builder)
* @Author: 唐欢
* @Date: 2022/6/20 10:22
* @Version 1.0
*/
public interface Builder {
/**
* 制作主食
*/
public void makeStapleFood(String stapleFood);
/**
* 制作主食
* @param complementaryFood
*/
public void makeComplementaryFood(String complementaryFood);
/**
* 配置玩具
* @param toy
*/
public void disToy(String toy);
/**
* 配置书籍
* @param drink
*/
public void disDrink(String drink);
public KFCChildrenMeal makeMeal();
}
具体建造者:实现Builder接口
构造和装配各个部件
/**
* @ClassName: ChildrenMealBuider
* @Description: 具体建造者 儿童餐
* @Author: 唐欢
* @Date: 2022/6/20 10:36
* @Version 1.0
*/
public class ChildrenMealBuider implements Builder{
KFCChildrenMeal kfcChildrenMeal = new KFCChildrenMeal();
@Override
public void makeStapleFood(String stapleFood) {
kfcChildrenMeal.setStapleFood(stapleFood);
}
@Override
public void makeComplementaryFood(String complementaryFood) {
kfcChildrenMeal.setComplementaryFood(complementaryFood);
}
@Override
public void disToy(String toy) {
kfcChildrenMeal.setToy(toy);
}
@Override
public void disDrink(String drink) {
kfcChildrenMeal.setDrink(drink);
}
@Override
public KFCChildrenMeal makeMeal() {
return kfcChildrenMeal;
}
}
指挥者:构建一个使用Builder接口的对象
它主要用于创建一个复杂的对象,
作用:
① 隔离了客户端与对象的生产过程
② 负责控制产品对象的生产过程
/**
* @ClassName: KfcDirector
* @Description: 指挥者
* @Author: 唐欢
* @Date: 2022/6/20 10:45
* @Version 1.0
*/
public class KfcDirector {
private ChildrenMealBuider childrenMealBuider;
public void setChildrenMealBuider(ChildrenMealBuider childrenMealBuider){
this.childrenMealBuider =childrenMealBuider;
}
public KFCChildrenMeal makeKFCChildrenMeal(String stapleFood,String complementaryFood,String toy,String drink){
childrenMealBuider.makeStapleFood(stapleFood);
childrenMealBuider.makeComplementaryFood(complementaryFood);
childrenMealBuider.disToy(toy);
childrenMealBuider.disDrink(drink);
return childrenMealBuider.makeMeal();
}
}
测试代码:
@Test
public void builder(){
ChildrenMealBuider childrenMealBuider = new ChildrenMealBuider();
KfcDirector kfcDirector =new KfcDirector();
kfcDirector.setChildrenMealBuider(childrenMealBuider);
KFCChildrenMeal kfcChildrenMeal =kfcDirector.makeKFCChildrenMeal("汉堡","玉米","小汽车","牛奶");
System.out.println(kfcChildrenMeal.toString());
}
使用建造者模式的好处:
①使用建造者模式可以使客户端不必知道产品内部组成的细节。
②具体的建造者类之间是相互独立的,对系统的扩展非常有利。
③由于具体的建造者是独立的,因此可以对建造过程逐步细化,而不对其他的模块产生任何影响。
在上面我们已经对建造者模式有了一定的了解,接下来我们就来看看mybatis是如何初始化中使用建造者模式创建对象的.
mybatis 初始化的主要工作是加载并解析mybatis-config配置文件,映射配置文件以及相关的注解信息.
Mybatis 的初始化入口是SqlSessionFactoryBuilder().build()方法,初始化入口代码如下:
在重载的build()方法中创建了XMLConfigBuilder对象读取并解析mybatis-config.xml 配置文件.
MyBatis在初始化的时候,会将MyBatis的配置信息全部加载到内存中,使用org.apache.ibatis.session.Configuration实例来维护.
Configuration对象的结构和xml配置文件的对象几乎相同。
mybatis-conf.xml 的配置标签有哪些:
properties (属性),settings (设置),typeAliases (类型别名),typeHandlers (类型处理
器),objectFactory (对象工厂),mappers (映射器)等;
Configuration也有对应的对象属性来封装它们
也就是说,初始化配置文件信息的本质就是创建Configuration对象,将解析的xml数据封装到Configuration内部属性中.
XML加载完成后调用XMLConfigBuilder.parse()方法进行解析.源代码如下:
在parse()方法中,调用parseConfiguration()方法对configuration的子节点进行解析.解析方法如下:
具体的节点解析方法可自行查看源码.
在mybatis 初始化时,除了加载mybatis-config.xml配置文件还,还会加载全部的映射配置文件,mybatis-config.xml配置文件中的节点会告诉mybatis去哪里查找映射文件以及使用了配置注解表示的接口. 节点定义:
<!--映射器:指定映射文件或映射类 必写-->
<mappers>
<!-- 方式一:使用相对于类路径的资源引用,开发中常用方式 -->
<mapper resource="mapper/UserMapper.xml"/>
<!-- 方式二:使用完全限定资源定位符(URL) -->
<mapper url="file:///mapper/UserMapper.xml"/>
<!-- 方式三:使用映射器接口实现类的完全限定类名 -->
<mapper class="org.mybatis.builder.AuthorMapper"/>
<!-- 方式四:将包内的映射器接口实现全部注册为映射器 -->
<package name="org.mybatis.builder"/>
</mappers>
XMLConfigBuilder.mapperElement() 方法是主要负责解析节点,它会创建XMLMapperBuilder 对象加载映射文件,如果映射配置文件存在相应的mapper接口,也会加载相应的Mapper接口,解析其中的注解并完成向MapperRegistry的注册,源码如下:
XMLMapperBuilder 负责解析映射配置文件,其中XMLMapperBuilder.parse()方法是解析映射文件的入口,具体代码如下:
XMLMapperBuilder同XMLConfigBuilder 一样,也是将每个节点的解析过程封装成一个方法.XMLMapperBuilder.configurationElement() 方法进行解析,解析的源代码如下:
每个节点具体的解析方式可自行查看源代码.
在mapper映射文件中,SQL节点主要定义SQL语句,SQL语句主要由XMLStatementBuilder 负责进行解析XMLStatementBuilder.parseStatementNode()方法是解析SQL节点的入口函数,源代码如下:
每个映射配置文件的命名空间可以绑定一个Mapper接口,并注册到MapperRegister中.在XMLMapperBuilder.bindMapperForNamespace()方法职工,完成映射配置文件与对应Mapper 接口的绑定,具体实现如下:
XMLMapperBuilder.configurationElement()方法解析映射配置文件时,是按照从文件头到文件尾的顺序解析的,但是有时候在解析一个节点时,会引用定义在该节点之后和还未解析的节点,就会导致解析失败并抛出BuilderException. 根据抛出异常的节点不同,Mabatis 会创建不同的Resolver对象.并添加到Configuration的不同incomplete集合中.
通过configurationElement() 方法完成一次映射配置文件后,会调用parsePendingResultMaps()、parsePendingCacheRefs()、parsePendingStatements()方法处理configuration中对应的三个incomplete*集合。以parsePendingStatements() 方法为例进行分析,具体实现如下:
到目前为止,mybatis的初始化过程就完成了。
mybatis初始化的类都集成了一个抽象类BaseBuilder,这个抽象类对常用的方法进行封装了。继承它的类图如下:
以XMLMapperBuilder为例来看看在初始化过程中的建造者模式,
BaseBuilder 抽象类在这扮演这建造接口的角色,
其中 XMLMapperBuilder 、XMLConfigBuilder、XMLStatementBuilder 都是具体建造者角色。但是XMLMapperBuilder 、XMLConfigBuilder、XMLStatementBuilder同时也扮演着Director角色。
在mybatis中,除了mybatis初始化使用了建造者模式,在cache 也是用了建造者模式。