Mybatis系列7:建造者模式在解析中的应用

1.什么是建造者模式

1.1 概念定义

建造者模式(也被称为“生成器模式”)将一个复杂对象的构建过程与它的表示分离,从而使得同样的构建过程可以创建不同的表示。建造者模式将一个复杂对象的创建过程分成了 一步步简单的步骤,用户只需要了解复杂对象的类型和内容,而无须关注复杂对象的具体构造过程,帮助用户屏蔽掉了复杂对象内部的具体构建细节。
建造者与工厂的区别
建造者模式更加注重方法的调用顺序,工厂模式注重于创建对象。
创建对象的力度不同,建造者模式创建复杂的对象,有各种复杂的部件组成,工厂模式创建出来的对象都一样。
关注重点不一样,工厂模式只需要把对象创建出来就可以了,而建造者模式中不仅要创建出这个对象,还要知道这个对象有哪些部件组成。
建造者模式根据建造过程中的顺序不一样,最终的对象部件组成也不一样。
功能
场景:建造一个复杂的产品时需要很多组件,装配这些子组件有个步骤问题。实际开发中,我们所需要的对象构建时,也非常复杂,有很多步骤需要处理,但是这些步骤的顺序无关紧要。
建造者模式的本质:分离了对象子组件的单独构造(由Builder来负责)和装配(由Director负责)。从而可以构建出复杂的对象。
这个模式适用于:创建对象需要很多步骤,但是步骤的顺序不一定固定的场景。
这里是将复杂对象的创建和使用进行了分离。
建造者有四个角色
1.产品:要创建的产品类对象。
2.建造者抽象 Builder:建造者的抽象类,规范产品对象的各个组成部分的建造,一般由子类实现具体的建造过程。
3.建造者ConcreteBuilder:具体的Builder类,根据不同的业务逻辑,具体完成各个组成部分的创建。
4.调用者Director:调用具体的建造者,来创建对象的各个部分,在指导者中不涉及具体产品的信息,只负责保证对象各部分完整创建或者按某种顺序创建。

1.2 例子

由于实现了构建和装配的解耦,不同的构建器,相同的装配,也可以做出不同的对象;相同的构建器,不同的装配顺序也可以做出不同的对象。也就是实现了构建算法,装配算法的解偶,实现了更好的复用。
实现方法:一个大实体中会有多个组件,为每个组件创建Bean,同时为这个大组件创建由小组件组成的Bean,之后创建两个接口文件,一个是用于描述创建大组件内各个小组件的Builder,另一个是描述将各个小组件组装成大组件的装配过程。之后根据不同情况分别实现这两个接口就行了。
例子:Tom老师的课里有个课程的例子,这里我们再造一个创建并组装一个自己的飞船的例子

public class AirShip {
    private OrbitalModule orbitalmodule;
    private Engine engine;
    private EscapeTower escapeTower;

    public OrbitalModule getOrbitalmodule() {
        return orbitalmodule;
    }

    public void setOrbitalmodule(OrbitalModule orbitalmodule) {
        this.orbitalmodule = orbitalmodule;
    }

    public Engine getEngine() {
        return this.engine;
    }

    public void setEngine(Engine engine) {
        this.engine = engine;
    }

    public EscapeTower getEscapeTower() {
        return escapeTower;
    }

    public void setEscapeTower(EscapeTower escapeTower) {
        this.escapeTower = escapeTower;
    }


}

//分别定义上面大组件使用的各个小组件的Bean

class OrbitalModule {
    private String name;

    public OrbitalModule(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

class Engine {
    private String name;

    public Engine(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

class EscapeTower {
    private String name;

    public EscapeTower(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

定义两个接口,一个用来统一创建各个小组件的Builder,另一个是将小组件组装成大组件。

public interface AirShipBuilder {
    OrbitalModule addeOrbitalModule();

    Engine addEngine();

    EscapeTower addEscapteTower();

}

public interface AirShipDirector {
    AirShip directorAirShip();
}

然后分别实现:

public class SxtAirShipBuilder implements AirShipBuilder {

    public SxtAirShipBuilder() {
    }

    
    public OrbitalModule addOrbitalModule() {
        return new OrbitalModule("qingchao轨道舱");
    }

    
    public Engine addEngine() {
        return new Engine("qingchao发动机");
    }

 
    public EscapeTower addEscapteTower() {
        return new EscapeTower("qingchao救生塔");
    }

}


public class SxtAirShipDirector implements AirShipDirector {
    private AirShipBuilder builder;

    public SxtAirShipDirector(AirShipBuilder builder) {
        this.builder = builder;
    }

   
    public AirShip directorAirShip() {
        Engine engine = builder.createEngine();
        OrbitalModule orbitalModule = builder.createOrbitalModule();
        EscapeTower escapeTower = builder.createEscapteTower();
       
        //装配成飞船
        AirShip ship=new AirShip();
        ship.setEngine(engine);
        ship.setEscapeTower(escapeTower);
        ship.setOrbitalmodule(orbitalModule);
        return ship;
    }
}

1.3 链式写法

上面的代码可以修改成如下格式的:

AirShipBuilder builder=new AirShip()
        .addEngine(engine)
        .addEscapeTower(escapeTower)
        .addOrbitalmodule(orbitalModule).build();

此时需要修改代码:

public interface AirShipBuilder {
    SxtAirShipBuilder addeOrbitalModule(String module);

    SxtAirShipBuilder addEngine(String engine);

    SxtAirShipBuilder addEscapteTower(String tower);

    }


public class SxtAirShipBuilder implements AirShipBuilder {

   private AirShip airShip;
    public SxtAirShipBuilder() {
        airShip=new AirShip();
    }

    public SxtAirShipBuilder addeOrbitalModule(String module) {
          airShip.setOrbitalmodule(new OrbitalModule(module));
          return this;
    }

    public SxtAirShipBuilder addEngine(String engine) {
          airShip.setEngine( new Engine(engine));
          return  this;
    }

    public SxtAirShipBuilder addEscapteTower(String tower) {
          airShip.setEscapeTower( new EscapeTower(tower));
          return this;
    }
    public  AirShip  build() {
        return this.airShip;
    }
}

调用的时候:

public static void main(String[] args) {
    AirShipBuilder builder= new SxtAirShipBuilder();
    AirShip airShip=   builder.addEngine("庆超 发动机").addeOrbitalModule("庆超 轨道舱").addEscapteTower(" 庆超 救生塔").build();
      airShip.getEngine();
}

这种情况下director就不需要了。

1.4 建造者模式的优缺点

优点:封装性好,创建和使用分离.
扩展性好,建造类之间独立,一定程度上解耦。
缺点:
1.产生多余的Builder对象
2.产品内部发生变化,建造者都要修改,成本较大。

2.建造者模式在Mybatis读取配置文件中的应用

Mybatis开始工作的入口在哪里呢?
MyBatis 初始化的主要工作是加载井解析 mybatis-config.xml 配置文件、映射配置文件以及相关的注解信息。 MyBatis的初始化入口是 SqlSessionFactoryBuilder.build()方法:

  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      // 用于解析 mybatis-config.xml,同时创建了 Configuration 对象 >>
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      // 解析XML,最终返回一个 DefaultSqlSessionFactory >>
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        inputStream.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

SqlSessionFactoryBuilder.build()方法会创建 XMLConfigBuilder 对象来解析mybatis-config.xml 配置文件 ,而 XMLConfigBuilder 是继承自BaseBuilder抽象类BaseBuilder 的子类。

2.1 BaseBuilder

BaseBuilder和其子类结构如图所示:
在这里插入图片描述
正如前面所说, MyBatis 的初始化过程使用了建造者模式,这里的 BaseBuilder 抽象类就扮演着建造者接口的角色。
XMLConfigBuilder负责解析mybatis-config.xml文件
XMLMapperBuilder负责解析负责解析mapper映射配置文件,它继承了 BaseBuilder 抽象类,也是具体建造者的角色。
XMLStatementBuilder: 用于SQL节点的解析。这些 SQL节点不同于一般的节点,因此使用独立的方法来解析相关语句。

看一下抽象类BaseBuilder的定义:

public abstract class BaseBuilder {
// MyBatis 初始化过程的核心对象, MyBatis 中几乎全部的配置信息会保存到Configuration 对象中 。 Configuration 对象是在 //MyBatis 初始化过程中创建且是全局唯一的。
  protected final Configuration configuration;
//用< typeAliases >标签定义别名,这些定义的别名都会记录在该TypeAliasRegistry 对象中	
  protected final TypeAliasRegistry typeAliasRegistry;
   //使用<typeAliases>标签定义别名,这些定义的别名都会记录在该TypeAliasRegistry 对象中,完成指定数据库类型与 Java //类型的转换,这些 TypeHandler 都会记录在 TypeHandlerRegistry 中
  protected final TypeHandlerRegistry typeHandlerRegistry;

  public BaseBuilder(Configuration configuration) {
    this.configuration = configuration;
    this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
    this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
  }

2.2 XMLConfigBuilder

XMLConfigBuilder它扮演的是具体建造者的角色。XMLConfigBuilder 主要负责解析 mybatis-config.xml 配置文件.
XMLConfigBuilder.parse()方法是解析 mybatis-config.xml 配置文件的入口,它通过调用
XMLConfigBuilder.parseConfiguration()方法实现整个解析过程

public class XMLConfigBuilder extends BaseBuilder {
//标识是否已经解析过 mybatis-config.xml 配置丈件
  private boolean parsed;
  //用于解析 mybatis -config.xml 配置文件的 XPathParser 对象
  private final XPathParser parser;
  //标识<environment>配置的名称,默认读取<environment>标签的 default 属性
  private String environment;
  //ReflectorFactory 负责创建和缓存 Reflector 对象
  private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();
public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    // XPathParser,dom 和 SAX 都有用到 >>
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

  private void parseConfiguration(XNode root) {
    try {
  
      // 对于全局配置文件各种标签的解析
      propertiesElement(root.evalNode("properties"));
      // 解析 settings 标签
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      loadCustomLogImpl(settings);
      // 类型别名
      typeAliasesElement(root.evalNode("typeAliases"));
      // 插件
      pluginElement(root.evalNode("plugins"));
      // 用于创建对象
      objectFactoryElement(root.evalNode("objectFactory"));
      // 用于对对象进行加工
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      // 反射工具箱
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      // settings 子标签赋值,默认值就是在这里提供的 >>
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631
      // 创建了数据源 >>
      environmentsElement(root.evalNode("environments"));
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      typeHandlerElement(root.evalNode("typeHandlers"));
      // 解析引用的Mapper映射器
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

parseConfigurationO方法的代码还是比较整洁的,我们可以清楚地看到, XMLConfigBuilder将mybatis-config.xml 配置文件中每个节点的解析过程封装成了一个相应的方法。

2.3 XMLMapperBuilder

XMLMapperBuilder 负责解析映射配置文件,它继承了 BaseBuilder 抽 象类,也是具体建造者的角色 。 XMLMapperBuilder.parse()方法是解析映射文件的入口

public class XMLMapperBuilder extends BaseBuilder {

  private final XPathParser parser;
  private final MapperBuilderAssistant builderAssistant;
  private final Map<String, XNode> sqlFragments;
  private final String resource;

  public void parse() {
    // 总体上做了两件事情,对于语句的注册和接口的注册
    if (!configuration.isResourceLoaded(resource)) {
      // 1、具体增删改查标签的解析。
      // 一个标签一个MappedStatement。
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      // 2、把namespace(接口类型)和工厂类绑定起来,放到一个map。
      // 一个namespace 一个 MapperProxyFactory 
      bindMapperForNamespace();
    }
    //处理 configurationElement ()方法中解析失败的<resultMap>节点
    parsePendingResultMaps();
    //处理 configurationElement ()方法中解析失败的<cache-ref>节点
    parsePendingCacheRefs();
    //处理 configurationElement ()方法中解析失败的 SQL 语句节点
    parsePendingStatements();
  }

2.4 XMLStatementBuilder

MyBatis 使用 SqISource 接口表示映射文件或注解中定义的 SQL 语句,但它表示的 SQL 语
句是不能直接被数据库执行的,因为其中可能含有动态 SQL 语句相关的节点或是占位符等需要
解析的元素。 SqISource 接口的定义如下:

public interface SqlSource {
  BoundSql getBoundSql(Object parameterObject);
}
public class XMLStatementBuilder extends BaseBuilder {

  private final MapperBuilderAssistant builderAssistant;
  private final XNode context;
  private final String requiredDatabaseId;

XMLStatementBuilder.parseStatementNode()方法是解析 SQL节点的入口函数,解析就不看了。

上面的接口BoundSql的实现类有:
在这里插入图片描述
这里对 SqlSource 接口 的各个实现做简单说明。DynamicSq!Source 负责处理动态 SQL 语句,
RawSqlSource 负责处理静态语句,两者最终都会将处理后的SQL语句封装成 StaticSqlSource返回。 DynamicSq!Source 与 StaticSq!Source 的主要区别是StaticSq!Source 中记录的 SQL 语句中可能含有 “?”占位符 , 但是可以直接提交给数据库执行。DynamicSqlSource 中封装的 SQL语句还需要进行一系列解析,才会最终形成数据库可执行的SQL语句。

从上面的分析我们可以看到解析xml是一个工作量巨大的工作,类型多,方式灵活,设置的内容也不一样,Mybatis通过三个Builder分别解析mybatis-config.xml,Mapper.xml和SQL,每个都是一个构造器。这三者构成了解析配置文件的骨架,具体实现太复杂了,以后再看。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

纵横千里,捭阖四方

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值