从 Mybatis 源码中,学习到的 10 种设计模式(二)

四、类型:结构型模式

1. 适配器模式

源码详见cn.bugstack.mybatis.logging.Log

public interface Log {
  boolean isDebugEnabled();
  boolean isTraceEnabled();
  void error(String s, Throwable e);
  void error(String s);
  void debug(String s);
  void trace(String s);
  void warn(String s);
}

复制代码

源码详见cn.bugstack.mybatis.logging.slf4j.Slf4jImpl

public class Slf4jImpl implements Log {
  private Log log;
  public Slf4jImpl(String clazz) {    Logger logger = LoggerFactory.getLogger(clazz);
    if (logger instanceof LocationAwareLogger) {      try {        // check for slf4j >= 1.6 method signature        logger.getClass().getMethod("log", Marker.class, String.class, int.class, String.class, Object[].class, Throwable.class);        log = new Slf4jLocationAwareLoggerImpl((LocationAwareLogger) logger);        return;      } catch (SecurityException e) {        // fail-back to Slf4jLoggerImpl      } catch (NoSuchMethodException e) {        // fail-back to Slf4jLoggerImpl      }    }
    // Logger is not LocationAwareLogger or slf4j version < 1.6    log = new Slf4jLoggerImpl(logger);  }
  @Override  public boolean isDebugEnabled() {    return log.isDebugEnabled();  }
  @Override  public boolean isTraceEnabled() {    return log.isTraceEnabled();  }
  @Override  public void error(String s, Throwable e) {    log.error(s, e);  }
  @Override  public void error(String s) {    log.error(s);  }
  @Override  public void debug(String s) {    log.debug(s);  }
  @Override  public void trace(String s) {    log.trace(s);  }
  @Override  public void warn(String s) {    log.warn(s);  }
}

复制代码

  • 适配器模式:是一种结构型设计模式,它能使接口不兼容的对象能够相互合作。

  • 场景介绍:正是因为有太多的日志框架,包括:Log4j、Log4j2、Slf4J 等等,而这些日志框架的使用接口又都各有差异,为了统一这些日志工具的接口,Mybatis 定义了一套统一的日志接口,为所有的其他日志工具接口做相应的适配操作。

  • 同类场景:主要集中在对日志的适配上,Log 和 对应的实现类,以及在 LogFactory 工厂方法中进行使用。

2. 代理模式

源码详见cn.bugstack.mybatis.binding.MapperProxy

public class MapperProxy<T> implements InvocationHandler, Serializable {
    private static final long serialVersionUID = -6424540398559729838L;
    private SqlSession sqlSession;    private final Class<T> mapperInterface;    private final Map<Method, MapperMethod> methodCache;
    public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {        this.sqlSession = sqlSession;        this.mapperInterface = mapperInterface;        this.methodCache = methodCache;    }
    @Override    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {        if (Object.class.equals(method.getDeclaringClass())) {            return method.invoke(this, args);        } else {            final MapperMethod mapperMethod = cachedMapperMethod(method);            return mapperMethod.execute(sqlSession, args);        }    }        // ...
}

复制代码

  • 代理模式:是一种结构型模式,让你能够提供对象的替代品或其占位符。代理控制着对原对象的访问,并允许在将请求提交给对象前进行一些处理。

  • 场景介绍:不吹牛的讲,没有代理模式,就不会有各类的框架存在。就像 Mybatis 中的 MapperProxy 映射器代理实现类,它所实现的功能就是帮助我们完成 DAO 接口的具体实现类的方法操作,你的任何一个配置的 DAO 接口所调用的 CRUD 方法,都会被 MapperProxy 接管,调用到方法执行器等一系列操作,并返回最终的数据库执行结果。

  • 同类场景DriverProxyPluginInvokerMapperProxy

3. 组合模式

源码详见cn.bugstack.mybatis.scripting.xmltags.SqlNode

public interface SqlNode {
    boolean apply(DynamicContext context);
}

复制代码

源码详见cn.bugstack.mybatis.scripting.xmltags.IfSqlNode

public class IfSqlNode implements SqlNode{
    private ExpressionEvaluator evaluator;    private String test;    private SqlNode contents;
    public IfSqlNode(SqlNode contents, String test) {        this.test = test;        this.contents = contents;        this.evaluator = new ExpressionEvaluator();    }
    @Override    public boolean apply(DynamicContext context) {        // 如果满足条件,则apply,并返回true        if (evaluator.evaluateBoolean(test, context.getBindings())) {            contents.apply(context);            return true;        }        return false;    }
}

复制代码

源码详见cn.bugstack.mybatis.scripting.xmltags.XMLScriptBuilder

public class XMLScriptBuilder extends BaseBuilder {
    private void initNodeHandlerMap() {        // 9种,实现其中2种 trim/where/set/foreach/if/choose/when/otherwise/bind        nodeHandlerMap.put("trim", new TrimHandler());        nodeHandlerMap.put("if", new IfHandler());    }     List<SqlNode> parseDynamicTags(Element element) {        List<SqlNode> contents = new ArrayList<>();        List<Node> children = element.content();        for (Node child : children) {            if (child.getNodeType() == Node.TEXT_NODE || child.getNodeType() == Node.CDATA_SECTION_NODE) {
            } else if (child.getNodeType() == Node.ELEMENT_NODE) {                String nodeName = child.getName();                NodeHandler handler = nodeHandlerMap.get(nodeName);                if (handler == null) {                    throw new RuntimeException("Unknown element " + nodeName + " in SQL statement.");                }                handler.handleNode(element.element(child.getName()), contents);                isDynamic = true;            }        }        return contents;    }        // ...}

复制代码

配置详见resources/mapper/Activity_Mapper.xml

<select id="queryActivityById" parameterType="cn.bugstack.mybatis.test.po.Activity" resultMap="activityMap" flushCache="false" useCache="true">    SELECT activity_id, activity_name, activity_desc, create_time, update_time    FROM activity    <trim prefix="where" prefixOverrides="AND | OR" suffixOverrides="and">        <if test="null != activityId">            activity_id = #{activityId}        </if>    </trim></select>

复制代码

  • 组合模式:是一种结构型设计模式,你可以使用它将对象组合成树状结构,并且能独立使用对象一样使用它们。

  • 场景介绍:在 Mybatis XML 动态的 SQL 配置中,共提供了 9 种(trim/where/set/foreach/if/choose/when/otherwise/bind)标签的使用,让使用者可以组合出各类场景的 SQL 语句。而 SqlNode 接口的实现就是每一个组合结构中的规则节点,通过规则节点的组装完成一颗规则树组合模式的使用。

  • 同类场景:主要体现在对各类 SQL 标签的解析上,以实现 SqlNode 接口的各个子类为主。

4. 装饰器模式

源码详见cn.bugstack.mybatis.session.Configuration

public Executor newExecutor(Transaction transaction) {    Executor executor = new SimpleExecutor(this, transaction);    // 配置开启缓存,创建 CachingExecutor(默认就是有缓存)装饰者模式    if (cacheEnabled) {        executor = new CachingExecutor(executor);    }    return executor;}

复制代码

  • 装饰器模式:是一种结构型设计模式,允许你通过将对象放入包含行为的特殊封装对象中来为原对象绑定新的行为。

  • 场景介绍:Mybatis 的所有 SQL 操作,都是经过 SqlSession 会话调用 SimpleExecutor 简单实现的执行器完成的,而一级缓存的操作也是在简单执行器中处理。那么这里二级缓存因为是基于一级缓存刷新操作的,所以在实现上,通过创建一个缓存执行器,包装简单执行器的处理逻辑,实现二级缓存操作。那么这里用到的就是装饰器模式,也叫俄罗斯套娃模式。

  • 同类场景:主要提前在 Cache 缓存接口的实现和 CachingExecutor 执行器中。

五、类型:行为型模式

1. 模板模式

源码详见cn.bugstack.mybatis.executor.BaseExecutor

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {    if (closed) {        throw new RuntimeException("Executor was closed.");    }    // 清理局部缓存,查询堆栈为0则清理。queryStack 避免递归调用清理    if (queryStack == 0 && ms.isFlushCacheRequired()) {        clearLocalCache();    }    List<E> list;    try {        queryStack++;        // 根据cacheKey从localCache中查询数据        list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;        if (list == null) {            list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);        }    } finally {        queryStack--;    }    if (queryStack == 0) {        if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {            clearLocalCache();        }    }    return list;}

复制代码

源码详见cn.bugstack.mybatis.executor.SimpleExecutor

protected int doUpdate(MappedStatement ms, Object parameter) throws SQLException {    Statement stmt = null;    try {        Configuration configuration = ms.getConfiguration();        // 新建一个 StatementHandler        StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);        // 准备语句        stmt = prepareStatement(handler);        // StatementHandler.update        return handler.update(stmt);    } finally {        closeStatement(stmt);    }}

复制代码

  • 模板模式:是一种行为设计模式,它在超类中定义了一个算法的框架,允许子类在不修改结构的情况下重写算法的特定步骤。

  • 场景介绍:只要存在一系列可被标准定义的流程,在流程的步骤大部分是通用逻辑,只有一少部分是需要子类实现的,那么通常会采用模板模式来定义出这个标准的流程。就像 Mybatis 的 BaseExecutor 就是一个用于定义模板模式的抽象类,在这个类中把查询、修改的操作都定义出了一套标准的流程。

  • 同类场景BaseExecutorSimpleExecutorBaseTypeHandler

2. 策略模式

源码详见cn.bugstack.mybatis.type.TypeHandler

public interface TypeHandler<T> {
    /**     * 设置参数     */    void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
    /**     * 获取结果     */    T getResult(ResultSet rs, String columnName) throws SQLException;
    /**     * 取得结果     */    T getResult(ResultSet rs, int columnIndex) throws SQLException;
}

复制代码

源码详见cn.bugstack.mybatis.type.LongTypeHandler

public class LongTypeHandler extends BaseTypeHandler<Long> {
    @Override    protected void setNonNullParameter(PreparedStatement ps, int i, Long parameter, JdbcType jdbcType) throws SQLException {        ps.setLong(i, parameter);    }
    @Override    protected Long getNullableResult(ResultSet rs, String columnName) throws SQLException {        return rs.getLong(columnName);    }
    @Override    public Long getNullableResult(ResultSet rs, int columnIndex) throws SQLException {        return rs.getLong(columnIndex);    }
}

复制代码

  • 策略模式:是一种行为设计模式,它能定义一系列算法,并将每种算法分别放入独立的类中,以使算法的对象能够互相替换。

  • 场景介绍:在 Mybatis 处理 JDBC 执行后返回的结果时,需要按照不同的类型获取对应的值,这样就可以避免大量的 if 判断。所以这里基于 TypeHandler 接口对每个参数类型分别做了自己的策略实现。

  • 同类场景PooledDataSource\UnpooledDataSourceBatchExecutor\ResuseExecutor\SimpleExector\CachingExecutorLongTypeHandler\StringTypeHandler\DateTypeHandler

3. 迭代器模式

源码详见cn.bugstack.mybatis.reflection.property.PropertyTokenizer

public class PropertyTokenizer implements Iterable<PropertyTokenizer>, Iterator<PropertyTokenizer> {
    public PropertyTokenizer(String fullname) {        // 班级[0].学生.成绩        // 找这个点 .        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);        }    }
    // ...
}

复制代码

  • 迭代器模式:是一种行为设计模式,让你能在不暴露集合底层表现形式的情况下遍历集合中所有的元素。

  • 场景介绍:PropertyTokenizer 是用于 Mybatis 框架 MetaObject 反射工具包下,用于解析对象关系的迭代操作。这个类在 Mybatis 框架中使用的非常频繁,包括解析数据源配置信息并填充到数据源类上,以及参数的解析、对象的设置都会使用到这个类。

  • 同类场景PropertyTokenizer

六、总结:“卷王”的心得

一份源码的成体系拆解渐进式学习,可能需要 1~2 个月的时间,相比于爽文和疲于应试要花费更多的经历。但你总会在一个大块时间学习完后,会在自己的头脑中构建出一套完整体系关于此类知识的技术架构,无论从哪里入口你都能清楚各个分支流程的走向,这也是你成为技术专家路上的深度学习。

  • 21
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值