MyBatis(15) MyBatis的设计模式

文章目录

  • MyBatis的设计模式
    • MyBatis使用到的设计模式
      • Builder构建者模式
        • 例子:使用构建者设计模式来生产computer
        • MyBatis中的体现
      • 工厂模式
        • 例子:生产电脑
        • MyBatis体现
      • 代理模式
        • 例子
        • MyBatis体现

MyBatis的设计模式

我们知道有3类23种设计模式,我们来看看MyBatis源码中使用了哪些设计模式

MyBatis使用到的设计模式

模式 MyBatis体现
Builder模式 例如SqlSessionFactoryBuilder、Environment
工厂方法模式 例如SqlSessionFactory、TransactionFactory、LogFactory
单例模式 例如ErrorContext、LogFactory
代理模式 MyBatis实现的核心,比如MapperProxy、ConnectionLogger 用的JDK动态代理
还有executor.loader包使用了cglib或者javassist达到延迟加载的效果
组合模式 例如SqlNode和各个子类ChooseSqlNode
模板方法模式 例如BaseExecutor和SimpleExecutor,还有BaseTypeHandler和所有子类例如InterTypeHandler
适配器模式 例如Log的MyBatis接口和它对JDBC、log4j等各种日志框架的适配实现
装饰者模式 例如Cache包中的cache.decorators子包中等各个装饰者的实现
迭代器模式 例如迭代器模式PropertyTokenizer

接下来对Builder构建者模式、工厂模式、代理模式进行解读,先简单介绍下模式自身,然后再解读在MyBatis中怎样应用了该模式

Builder构建者模式

Builder模式的定义是 将一个辅助对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示,它属于创建类模式。一般来说,如果一个对象的构建比较复杂,超出了构造函数所能包含的范围,就可以使用工厂模式或Builder模式。相当于工厂模式会产出一个完整的产品,Builder模式应用于更加复杂对象的构建,甚至只会侯建产品的一个部分。直白来说,就是使用多个简单的对象一步一步构建成一个复杂对象

例子:使用构建者设计模式来生产computer

主要步骤:

  • 将需要构建的目标类分成多个部件(电脑可以分为主机、显示器、键盘、鼠标等)

    public class Computer {
    
        private String displayer;
    
        private String mainUnit;
    
        private String mouse;
    
        private String keyboard;
    }
    
  • 创建构建类

    public class ComputerBuilder {
    
        private Computer computer = new Computer();
    
        public void installDisplayer(String displayer){
            computer.setDisplayer(displayer);
        }
    
        public void installMainUnit(String mainUnit){
            computer.setMainUnit(mainUnit);
        }
    
        public void installMouse(String mouse){
            computer.setMouse(mouse);
        }
    
        public void installKeyboard(String keyboard){
            computer.setKeyboard(keyboard);
        }
    
        public Computer getComputer(){
            return computer;
        }
    
    }
    
  • 依次创建部件并将部件组装成目标对象

    /**
     * 构建者模式
     * @author Administrator
     */
    public class ConstructorTest {
    
        public static void main(String[] args) {
            ComputerBuilder computerBuilder = new ComputerBuilder();
            computerBuilder.installDisplayer("显示器");
            computerBuilder.installMainUnit("主机");
            computerBuilder.installMouse("鼠标");
            computerBuilder.installKeyboard("键盘");
    
            Computer computer = computerBuilder.getComputer();
            System.out.println(computer);
        }
    
    }
    

MyBatis中的体现

SqlSessionFactory 的构建过程:

MyBatis的初始化工作非常复杂,不是只用一个构造函数就能搞定的。所有使用了构建者模式,使用了打开的Builder,进行分层构造,核心对象Configuration使用了XMLConfigBuilder来进行构造
MyBatis(15) MyBatis的设计模式_第1张图片
在Mybatis环境的初始化过程中,SqlSessionFactoryBuilder会调用XMLConfigBuilder读取MybatisMapConfig.xml和所有的*Mapper.xml文件,构建MyBatis运行的核心对象Configuration,然后将改Configuration对象作为参数构建一个SqlSessionFactory对象。

private void parseConfiguration(XNode root) {
    try {
        //issue #117 read properties first
        // 解析标签
        propertiesElement(root.evalNode("properties"));
        // 解析标签
        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"));
        settingsElement(settings);
        // read it after objectFactory and objectWrapperFactory issue #631
        // 解析标签
        environmentsElement(root.evalNode("environments"));
        // 解析标签
        databaseIdProviderElement(root.evalNode("databaseIdProvider"));
        // 解析标签
        typeHandlerElement(root.evalNode("typeHandlers"));
        // 解析标签
        mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
        throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
}

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

在这个过程中,有一个相似的特点,就是这些Builder会读取文件或配置,然后做大了的XpathParser解析、配置或者语法解析、反射生成对象、存入结果缓存等步骤,这么多的工作不是一个构造函数能包括的因此采用了Builder模式来解决

MyBatis(15) MyBatis的设计模式_第2张图片

工厂模式

在MyBatis中比如SqlSessionFactory使用的是工厂模式,该工厂没有那么复杂的逻辑,是一个简单工厂模式。

简单工厂模式(Simple Factory Pattern): 又称静态工厂方法模式(Static Factory Method),它属于创建型模式

在简单工厂模式中,可以根据参数的不同,返回不同类的实例。简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类

例子:生产电脑

假设有衣蛾电脑的代工生产商,它可以生产联想电脑,随着业务的拓展,这个工厂还能生产惠普电脑了,我们可以用一个单独的类来专门生产电脑,这就用到了简单工厂模式。

  • 创建抽象类computer

    public abstract class Computer {
        public abstract void start();
    }
    
  • 创建具体产品类

    public class HpComputer extends Computer{
        @Override
        public void start() {
            System.out.println("HP电脑启动");
        }
    }
    
    public class LenovoComputer extends Computer{
        @Override
        public void start() {
            System.out.println("联想电脑启动");
        }
    }
    
  • 创建工厂类

    public class ComputerFactory {
        
        public static Computer createComputer(String type) {
            Computer computer = null;
            switch (type){
                case "lenovo":
                    computer = new LenovoComputer();
                    break;
                case "HP":
                    computer = new HpComputer();
                    break;
            }
            return computer;
        }
    }
    
  • 测试

    /**
     * 简单工厂模式
     * @author Administrator
     */
    public class simpleComputerTest {
    
        public static void main(String[] args) {
            Computer computer = ComputerFactory.createComputer("lenovo");
            computer.start(); // 联想电脑启动
        }
    }
    

MyBatis体现

MyBatis中执行Sql语句、获取Mapper、管理事务的核心接口SqlSession的创建过程使用到了工厂模式。

有一个SqlSessionFactory来负责SqlSession的创建
MyBatis(15) MyBatis的设计模式_第3张图片
可以看到,SqlSessionFactoryopenSession() 方法重载了多个,分别支持 autoCommit、ExecutorType、TransactionIsolationLevel等参数的输入,来构建核心的SqlSession对象。

在DefaultSqlSessionFactory 的默认工厂实现里,有一个方法可以看出工厂怎么生产出SqlSession:

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
        final Environment environment = configuration.getEnvironment();
        final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
        tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
        // 根据参数创建指定类型的Executor
        final Executor executor = configuration.newExecutor(tx, execType);
        // 返回的是DefaultSqlSession对象
        return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
        closeTransaction(tx); // may have fetched a connection so lets call close()
        throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
        ErrorContext.instance().reset();
    }
}

这是一个openSession 调用的底层方法,该方法先从 Configuration 读取相应的环境配置,然后初始化 TransactionFactory 获得一个 Transaction 对象,然后通过 Transaction 获取一个 Executor对象,最后通过configuration, executor, autoCommit 三个参数构建了 SqlSession

代理模式

代理模式(Proxy Pattern)给某一个对象提供一个代理,并由代理对象控制对原对象的引用。 代理模式是一种对象结构型模式,代理模式分为静态代理和动态代理,我们这里来介绍下动态代理

例子

  • 创建一个抽象类,使其拥有一个没有返回值得doSomething方法

    public interface Person {
        void doSomething();
    }
    
  • 创建一个Person实现类Man,实现doSomething方法

    public class Man implements Person{
        @Override
        public void doSomething() {
            System.out.println("Man Do Something");
        }
    }
    
  • 创建JDK动态代理类,使其实现 InvocationHandler 接口,拥有一个Person属性,并创建 getTarget 获取代理对象方法

    public class JDKDynamicProxy implements InvocationHandler {
    
        /**
         * 声明被代理的对象
         */
        private Person person;
    
        /**
         * 构造函数
         */
        public JDKDynamicProxy(Person person){
            this.person = person;
        }
    
        /**
         * 获取代理对象
         */
        public Object getTarget(){
            return Proxy.newProxyInstance(person.getClass().getClassLoader(),person.getClass().getInterfaces(),this);
        }
    
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("对原方法进行前置增强");
            // 原方法执行
            Object invoke = method.invoke(person, args);
            System.out.println("对原方法进行后置增强");
    
            return invoke;
        }
    }
    
  • 测试

    /**
     * 代理模式
     */
    public class ProxyTest {
        public static void main(String[] args) {
    
            System.out.println("不使用代理类调用doSomething");
            Person man = new Man();
            man.doSomething();
    
            System.out.println("************************");
    
            System.out.println("使用代理类调用doSomething");
            Person proxyMan = (Person)new JDKDynamicProxy(new Man()).getTarget();
            proxyMan.doSomething();
        }
    }
    

    输出

    不使用代理类调用doSomething
    Man Do Something
    ************************
    使用代理类调用doSomething
    对原方法进行前置增强
    Man Do Something
    对原方法进行后置增强
    

MyBatis体现

代理模式可以认为是MyBatis的核心使用的模式,正式由于这个模式,我们只需编写 Mapper接口,不需要实现,由MyBatis 底层帮我们完成具体SQL的执行。

当我们使用Configuration的getMapper方法时,会调用MapperRegistry.getMapper方法,而该方法又会调用MapperProxyFactory.newInstance(sqlSession)来生产一个具体的代理:

package org.apache.ibatis.binding;

public class MapperProxyFactory<T> {

  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>();

  public MapperProxyFactory(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  public Class<T> getMapperInterface() {
    return mapperInterface;
  }

  public Map<Method, MapperMethod> getMethodCache() {
    return methodCache;
  }

  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

}

在这里,先通过 public T newInstance(SqlSession sqlSession) 方法会得到一个MapperProxy对象,然后调用 protected T newInstance(MapperProxy mapperProxy) 生产代理对象后返回。

而查看MapperProxy 的代码,可以看到以下内容:

package org.apache.ibatis.binding;

public class MapperProxy<T> implements InvocationHandler, Serializable {
    
    private final SqlSession sqlSession;
    private final Class<T> mapperInterface;
    private final Map<Method, MapperMethod> methodCache;

	...

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, args);
            } else if (method.isDefault()) {
                return invokeDefaultMethod(proxy, method, args);
            }
        } catch (Throwable t) {
            throw ExceptionUtil.unwrapThrowable(t);
        }
        final MapperMethod mapperMethod = cachedMapperMethod(method);
        return mapperMethod.execute(sqlSession, args);
    }
    
    ...
        
}

非常典型的,该MapperProxy类实现了InvocationHandler 接口,并且实现了invoke方法,通过这种方式,我们只需编写Mapper接口类,当真正执行一个Mapper接口时,就会转发给MapperProxy.invoke方法,而该方法则会调用后续的 SqlSession 的 CURD接口 完成sql的执行和返回

你可能感兴趣的:(MyBatis)