设计模式经典实践-Mybatis源码解析

2019-05-20  本文已影响0人  大方一号

前言

Mybatis应该是当前已知的主流框架源码阅读成本最低,设计最为简洁友好的框架。
设计模式差不多是框架设计者和阅读者的潜在遵守的规约,如果双方都按照这个套路来,读写双方都很愉快。
如果把编码比作文章的话,设计模式差不多也是整个行文的中心思想和脉络,按照既定模式来撸,没毛病!




涉及到的设计模式 : 工厂模式 建造者模式
涉及的设计模式:单例模式 代理模式

一、工厂模式

Mybatis获取数据源的方式就用了工厂模式,设计简洁,可拓展性好。
工厂模式是Java对象实例化的一种解决方案。

数据源工厂模式类图
public interface DataSource   {
 
  Connection getConnection() throws SQLException;

  Connection getConnection(String username, String password);
}
public interface DataSourceFactory {

  void setProperties(Properties props);

  DataSource getDataSource();

}
 //JNDI获取数据源
 public class JndiDataSourceFactory implements DataSourceFactory 
.....
//不使用连接池的简单实现
 public class UnpooledDataSourceFactory implements DataSourceFactory 
.....
//使用连接池的简单实现
 public class PooledDataSourceFactory extends UnpooledDataSourceFactory 
....
public class PooledDataSource implements DataSource {

  public PooledDataSource() {
    dataSource = new UnpooledDataSource();
  }

  public PooledDataSource(UnpooledDataSource dataSource) {
    this.dataSource = dataSource;
  }

  public PooledDataSource(String driver, String url, String username, String password) {
    dataSource = new UnpooledDataSource(driver, url, username, password);
    expectedConnectionTypeCode = assembleConnectionTypeCode(dataSource.getUrl(), dataSource.getUsername(), dataSource.getPassword());
  }
<dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
</dataSource>

产品接口&工厂接口就是设计中常见的顶层模块
实现类就是可变模块

工厂模式是我们最常用的实例化对象模式了,是代替new操作的一种模式。
工厂方法确实为系统结构提供了非常灵活强大的动态扩展机制,增加拓展功能,只需添加对应的工厂接口和产品实现接口的一对实现,系统其他地方无需变更。
工厂方法类比于建造者Build模式,前者偏向于类的实例化(new出来),后者侧重于类的初始化构建,属性填充,不再是简单new.

二、装饰器模式

通过组合的方式动态地给一个对象添加一些额外的职责或者行为

为支持每一种组合将产生大量的子类,使得子类数目呈现组合叠加爆炸性增长)

X x=new X1(new X2(new X3()))......

UML类图

装饰器模式-UML

一层一层嵌套,"装饰器类" 持有目标对象的引用,具体方法执行委托给具体的目标对象子类形成了一连串"装饰器链",不断地增强功能

应用场景1

mybatis 二级缓存
 Cache cache = new CacheBuilder(currentNamespace)
        .implementation(valueOrDefault(typeClass, PerpetualCache.class))
        .addDecorator(valueOrDefault(evictionClass, LruCache.class))
        .clearInterval(flushInterval)
        .size(size)
        .readWrite(readWrite)
        .blocking(blocking)
        .properties(props)
        .build();

  if (PerpetualCache.class.equals(cache.getClass())) {
      for (Class<? extends Cache> decorator : decorators) {
        cache = newCacheDecoratorInstance(decorator, cache);
        setCacheProperties(cache);
      }
      cache = setStandardDecorators(cache);
    } else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
      cache = new LoggingCache(cache);
    }
 
     try {
      MetaObject metaCache = SystemMetaObject.forObject(cache);
      if (size != null && metaCache.hasSetter("size")) {
        metaCache.setValue("size", size);
      }
      if (clearInterval != null) {
        cache = new ScheduledCache(cache);
        ((ScheduledCache) cache).setClearInterval(clearInterval);
      }
      if (readWrite) {
        cache = new SerializedCache(cache);
      }
      cache = new LoggingCache(cache);
      cache = new SynchronizedCache(cache);
      if (blocking) {
        cache = new BlockingCache(cache);
      }
      return cache;
    } catch (Exception e) {
      throw new CacheException("Error building standard cache decorators.  Cause: " + e, e);
    }
四不四很符合X x=new X1(new X2(new X3()))......[呲牙]
 public class FifoCache implements Cache {
  private final Cache delegate;
  public FifoCache(Cache delegate) {
    this.delegate = delegate;
    this.keyList = new LinkedList<Object>();
    this.size = 1024;
  }
//put get实际上都委托给了PerpetualCache来实现
  public void putObject(Object key, Object value) {
    cycleKeyList(key);
    delegate.putObject(key, value);//增强了回收策略
  }
  public Object getObject(Object key) {
    return delegate.getObject(key);
  }
//增强的方法,先回收最先进入的换成对象
 private void cycleKeyList(Object key) {
    keyList.addLast(key);
    if (keyList.size() > size) {
      Object oldestKey = keyList.removeFirst();
      delegate.removeObject(oldestKey);
    }
  }

Cache的设计上目标类和装饰器类实现了Cache接口,装饰器一个一个装饰器类串起来,层层包装目标类形成一个链

增强的功能一览

应用场景2

JDK IO流
FileInputStream in=new FileInputStream(new File ("hello.txt"));

BufferedInputStream inBuffered=new BufferedInputStream (in);

BufferedInputStream是一层装饰,增强了“缓冲区”的功能....

等等.....


三、模板设计模式 & 策略模式

定义一个操作中算法的骨架或流程,充分利用"多态"使得子类可以不改变算法的结构即可重新定义实现

适用场景

完成一件事情,有固定的流程步骤比如说 1->2->3->4,但是每个步骤根据子类对象的不同,而实现细节不同,就可以在父类中定义不变的方法,把可变的方法通过子类回调来实现

关键字: 回调

image
 public abstract class Abstractxxxxxx{
      
    public  void method{
         //校验逻辑
         //参数装配
        //业务逻辑1、2、3
         doMethod();
         //异常处理
         //资源清理
    }
    //抽象方法由子类实现,父类回调...
    protected abstract void doMethod() ;
}

mybatis Executor执行器

mybatis-执行器

动态的改变对象的行为,实现某一个功能有多种算法或者策略,多种不同解决方案动态切换,起到改变对象行为的效果,一般会结合模板方法模式配合使用

  
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    // 一级二级缓存处理逻辑....
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
      queryStack++;
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        //真正的查询db入口
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
   //todo 一些清理方法,缓存,事务,连接关闭等等
   查询db方法入口
 private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) {
   
    //省略....
    //local缓存
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
     //doQuery是抽象方法,可变方法交给具体的子类去实现
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      localCache.removeObject(key);
    }
    //后置处理.....
    return list;


//protected abstract 暗示着需要子类去实现
 protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
     
update/delete/insert套路同上
public class SimpleExecutor extends BaseExecutor { 
  //真正查询db的入口实现
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

方法调用,根据ExecutorType类型不同选择不同的实现类

 sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
 sqlSession = sqlSessionFactory.openSession(ExecutorType.SIMPLE);

Executor选择器入口

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
     //根据参数选择执行器实现 -> 策略模式
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);
    }
    if (cacheEnabled) {
      //上文装饰器的包装入口 -> 装饰器模式
      executor = new CachingExecutor(executor);
    }
   //插件拦截器链的入口-> 责任链模式
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

四、适配器模式

将一个类的接口转换成客户希望的另外一个接口

应用场景

mybatis 日志框架适配
package org.apache.ibatis.logging;

public interface Log {

  boolean isDebugEnabled();
  void error(String s, Throwable e);
  debug
  tarce
  warn
  .....
}
package org.apache.ibatis.logging.log4j;
import org.apache.ibatis.logging.Log;
import org.apache.log4j.Logger;

/**
 * @author Eduardo Macarron
 */
public class Log4jImpl implements Log {
  private Logger log;
  public void error(String s, Throwable e) {
    log.log(FQCN, Level.ERROR, s, e);
  }
 

其他日志适配器类也一样,继承"org.apache.ibatis.logging.Log"类,里面持有对第三方日志框架的日志记录类的引用

    <settings>
        <setting name="logImpl" value="LOG4J" />
    </settings>
  Class<? extends Log> logImpl = (Class<? extends Log>)resolveClass(
  props.getProperty("logImpl"));
  configuration.setLogImpl(logImpl);

Mybatis没有自己的日志系统,依赖于第三方实现,通过配置文件参数根据Logfactory来适配对应的第三方日志系统(log4j,jdk-log,commonslogging)过程略,可参照slf4j日志适配模式


五、快速讲解门面模式之SLF4J

子系统:职责单一,易于维护
门面:充当了客户类与子系统类之间的“第三者”,对客户端隐藏了很多细节,也就是"最少知道",比较契合迪米特法则

迪米特法则:如果两个类不必彼此直接通向,那么这两个类就不应当发生直接的相互作用。如果其中一个类需要调用另一个累哦的某一个方法的话,可以通过第三者转发这个调用
门面模式的Facade充当这个第三者
同理->自行脑补:门面模式也比较符合依赖倒转原则 Facade充当中间层-抽象接口

五 代理模式

为其他对象提供一种代理以控制对这个对象的访问,可能是主流开源框架使用频率最高的设计模式!
主要有3个角色:访问者代理人被代理人

静态代理中每个代理类只能为一个接口服务,这样程序开发中必然会产生许多的代理类,冗杂的代理类阅读起来也是一种灾难!
所以我们就会想办法可以通过一个代理类完成全部的代理功能,那么我们就需要用动态代理

//全局配置获取mapper对象
 public <T> T getMapper(Class<T> type) {
    return configuration.<T>getMapper(type, this);
  }
// mapper注册工厂MapperRegistry获取,Mybatis启动会扫描所有的Mapper接口
 public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    // knownMappers是mapper代理的缓存map
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      //key code 最终调用 `newInstance` 方法
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

 protected T newInstance(MapperProxy<T> mapperProxy) {
    //JDK动态代理生成对应的mapper接口
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

// Mapper接口调用会执行如下逻辑
 public Object invoke(Object proxy, Method method, Object[] args) {
    //省略.....
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    // 交给MapperMethod处理
    return mapperMethod.execute(sqlSession, args);
  }

小结:cachedMapperMethod方法维护了一个接口名+方法名的map集合,和Xml文件中的 namespace+ id 标签一一对应
伪代码

<mapper namespace="com.wacai.wodlee.service.xxxx">

<select id="selectJobs" .....>
 
  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    if (SqlCommandType.INSERT == command.getType()) {
    //如果对应的类型是insert ,也就是对应Xml文件中的<insert>标签
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.insert(command.getName(), param));
    } else if (SqlCommandType.UPDATE == command.getType()) {
      //如果对应的类型是update ,也就是对应Xml文件中的<update>标签
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.update(command.getName(), param));
    } else if (SqlCommandType.DELETE == command.getType()) {
     //如果对应的类型是delete ,也就是对应Xml文件中的<delete>标签
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.delete(command.getName(), param));
    } else if (SqlCommandType.SELECT == command.getType()) {
     //如果对应的类型是select ,也就是对应Xml文件中的<select>标签
      
      if (method.returnsVoid() && method.hasResultHandler()) {
        executeWithResultHandler(sqlSession, args);
        result = null;
      } else if (method.returnsMany()) {
         //返回多个对象,通常为List
        result = executeForMany(sqlSession, args);
      } else if (method.returnsMap()) {
        //返回为xml定义的map映射对象,做db字段和属性映射
        result = executeForMap(sqlSession, args);
      } else {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = sqlSession.selectOne(command.getName(), param);
      }
    } 
 
    return result;
  }
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)   {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      //StatementHandler封装了原生的Statement 
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }
   //1、创建SessionFactory
sqlSessionFactory=new SqlSessionFactoryBuilder().build(reader);
  //2、打开Session
 session=sqlSessionFactory.openSession();
  //3、获取用户接口mapper对象
UserMapper userMapper=session.getMapper(UserMapper.class);
  //4、执行crud方法
 User user=userMapper.selectUserById(1000L);
 // 最终实际执行的CRUD方法实际上是代理类执行Executor进行JDBC的常规操作.

六 建造者模式

建造者模式一般用来构建复杂对象,区别于工程模式实例化简单对象.
建造者模式屏蔽了对象创建的复杂细节,对象的构造和表示相分离
BUILD模式类图

UML
Product-产品角色: 一个具体的产品对象

Builder-抽象建造者:创建一个Product对象的各个部件指定的抽象接口

ConcreteBuilder-具体建造者:实现抽象接口,构建和装配填充属性

Director-指挥者: 主要是用于创建一个复杂的对象。它主要有两个作用,一是:隔离了客户与对象的生产过程,二是:负责控制产品对象的生产过程。

Mybatis中关于建造者模式的应用:我们先看看Mybatis是如何集成到Spirng容器中的,通过 Spring来管理,完美兼容?
其实很多开源框架都有集成spring的需求:借助spring的可拓展机制开发一个第三发的插件来完成对自身的集成和适配

//配置sqlSessionFactory,SqlSessionFactoryBean是FactoryBean的一种典型实现
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <!-- 配置数据库表对应的java实体类 -->
        <property name="typeAliasesPackage" value="com.xxx." />
        <!-- 自动扫描xml目录, 省掉Configuration.xml里的手工配置 -->
        <property name="mapperLocations" value="classpath:xxx/xxmappers/*.xml" />
    </bean>
//.....

FactoryBeanBeanFactory的区别是一个老生常谈的问题

BeanFactory提供了IOC容器最基本的形式,给具体的IOC容器的实现提供了规范,暂时不展开...

FactoryBean为IOC容器中Bean的实现提供了更加灵活的方式,给Bean的实现加上了一个简单工厂模式和装饰模式 : 可以在getObject()方法中灵活配置,完成复杂Bean的装配
FactoryBean通常用来构造复杂的bean和属性填充(简单的配置无法实现)

关于spring的可拓展机制,有机会我会单独讲解.

public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory> {

 //getObject方法返回一个构建完成的sqlSessionFactory
 public SqlSessionFactory getObject() throws Exception {
    if (this.sqlSessionFactory == null) {
      afterPropertiesSet();
    }
    return this.sqlSessionFactory;
  }
 //实际调用buildSqlSessionFactory来创建..... InitializingBean接口实现
  public void afterPropertiesSet() throws Exception {
    this.sqlSessionFactory = buildSqlSessionFactory();
  }

}
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {

     //待创建的全局配置Configuration,也就是带装配的  `Product` 角色
    Configuration configuration;
    //Xml方式build全局配置
    XMLConfigBuilder xmlConfigBuilder = null;
    if (this.configLocation != null) {
      xmlConfigBuilder = new XMLConfigBuilder(configLocation.getInputStream(), 
null, this.configurationProperties);
      configuration = xmlConfigBuilder.getConfiguration();
    }  

    //省略.....

    return this.sqlSessionFactoryBuilder.build(configuration);
  }
    //最终创建一个包装了Configuration的SqlSessionFactory
   public SqlSessionFactory build(Configuration config) {
        return new DefaultSqlSessionFactory(config);
    }

public class SqlSessionFactoryBuilder {
 //省略.......
 public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }

}

Mapper接口Bean并没有实现类,如何能实现自动装配和依赖注入?

   //mapper扫描和装配配置
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.xxx" />
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
    </bean>

BeanFactoryPostProcessor可以插手Spring-bean的实例化过程...(另一个利器是BeanPostProcessor,大家可以自行了解

该可拓展接口的在MybatisSpring集成的作用简单总结起来就是Spring若干个手动操作


三克油
上一篇 下一篇

猜你喜欢

热点阅读