关于java:Mybatis-使用的-9-种设计模式真是太有用了

35次阅读

共计 12488 个字符,预计需要花费 32 分钟才能阅读完成。

  • 1、Builder 模式
  • 2、工厂模式
  • 3、单例模式
  • 4、代理模式
  • 5、组合模式
  • 6、模板办法模式
  • 7、适配器模式
  • 8、装璜者模式
  • 9、迭代器模式

尽管咱们都晓得有 26 个设计模式,然而大多停留在概念层面,实在开发中很少遇到,Mybatis 源码中应用了大量的设计模式,浏览源码并察看设计模式在其中的利用,可能更深刻的了解设计模式。

Mybatis 至多遇到了以下的设计模式的应用:

  1. Builder 模式,例如 SqlSessionFactoryBuilder、XMLConfigBuilder、XMLMapperBuilder、XMLStatementBuilder、CacheBuilder;
  2. 工厂模式,例如 SqlSessionFactory、ObjectFactory、MapperProxyFactory;
  3. 单例模式,例如 ErrorContext 和 LogFactory;
  4. 代理模式,Mybatis 实现的外围,比方 MapperProxy、ConnectionLogger,用的 jdk 的动静代理;还有 executor.loader 包应用了 cglib 或者 javassist 达到提早加载的成果;
  5. 组合模式,例如 SqlNode 和各个子类 ChooseSqlNode 等;
  6. 模板办法模式,例如 BaseExecutor 和 SimpleExecutor,还有 BaseTypeHandler 和所有的子类例如 IntegerTypeHandler;
  7. 适配器模式,例如 Log 的 Mybatis 接口和它对 jdbc、log4j 等各种日志框架的适配实现;
  8. 装璜者模式,例如 Cache 包中的 cache.decorators 子包中等各个装璜者的实现;
  9. 迭代器模式,例如迭代器模式 PropertyTokenizer;

接下来挨个模式进行解读,先介绍模式本身的常识,而后解读在 Mybatis 中怎么利用了该模式。

1、Builder 模式

Builder 模式的定义是“将一个简单对象的构建与它的示意拆散,使得同样的构建过程能够创立不同的示意。”,它属于创立类模式,一般来说,如果一个对象的构建比较复杂,超出了构造函数所能蕴含的范畴,就能够应用工厂模式和 Builder 模式,绝对于工厂模式会产出一个残缺的产品,Builder 利用于更加简单的对象的构建,甚至只会构建产品的一个局部。

在 Mybatis 环境的初始化过程中,SqlSessionFactoryBuilder 会调用 XMLConfigBuilder 读取所有的 MybatisMapConfig.xml 和所有的 *Mapper.xml 文件,构建 Mybatis 运行的外围对象 Configuration 对象,而后将该 Configuration 对象作为参数构建一个 SqlSessionFactory 对象。

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

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

对于 builder 的具体类,办法都大都用 build* 结尾,比方 SqlSessionFactoryBuilder 为例,它蕴含以下办法:

即依据不同的输出参数来构建 SqlSessionFactory 这个工厂对象。

2、工厂模式

在 Mybatis 中比方 SqlSessionFactory 应用的是工厂模式,该工厂没有那么简单的逻辑,是一个简略工厂模式。

简略工厂模式 (Simple Factory Pattern):又称为动态工厂办法(Static Factory Method) 模式,它属于类创立型模式。在简略工厂模式中,能够依据参数的不同返回不同类的实例。简略工厂模式专门定义一个类来负责创立其余类的实例,被创立的实例通常都具备独特的父类。

SqlSession 能够认为是一个 Mybatis 工作的外围的接口,通过这个接口能够执行执行 SQL 语句、获取 Mappers、治理事务。相似于连贯 MySQL 的 Connection 对象。

能够看到,该 Factory 的 openSession 办法重载了很多个,别离反对 autoCommit、Executor、Transaction 等参数的输出,来构建外围的 SqlSession 对象。

在 DefaultSqlSessionFactory 的默认工厂实现里,有一个办法能够看出工厂怎么产出一个产品:

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);
            final Executor executor = configuration.newExecutor(tx, execType);
            returnnew 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。

在这里其实也能够看到端倪,SqlSession 的执行,其实是委托给对应的 Executor 来进行的。

而对于 LogFactory,它的实现代码:

publicfinalclass LogFactory {
    privatestatic Constructor<? extends Log> logConstructor;

    private LogFactory() {// disable construction}

    public static Log getLog(Class<?> aClass) {return getLog(aClass.getName());
    }

这里有个特地的中央,是 Log 变量的的类型是 Constructor<? extends Log>,也就是说该工厂生产的不只是一个产品,而是具备 Log 公共接口的一系列产品,比方 Log4jImpl、Slf4jImpl 等很多具体的 Log。

3、单例模式

单例模式(Singleton Pattern):单例模式确保某一个类只有一个实例,而且自行实例化并向整个零碎提供这个实例,这个类称为单例类,它提供全局拜访的办法。

单例模式的要点有三个:一是某个类只能有一个实例;二是它必须自行创立这个实例;三是它必须自行向整个零碎提供这个实例。单例模式是一种对象创立型模式。单例模式又名单件模式或单态模式。

在 Mybatis 中有两个中央用到单例模式,ErrorContext 和 LogFactory,其中 ErrorContext 是用在每个线程范畴内的单例,用于记录该线程的执行环境错误信息,而 LogFactory 则是提供给整个 Mybatis 应用的日志工厂,用于取得针对我的项目配置好的日志对象。

ErrorContext 的单例实现代码:

publicclass ErrorContext {privatestaticfinal ThreadLocal<ErrorContext> LOCAL = new ThreadLocal<ErrorContext>();

    private ErrorContext() {}

    public static ErrorContext instance() {ErrorContext context = LOCAL.get();
        if (context == null) {context = new ErrorContext();
            LOCAL.set(context);
        }
        return context;
    }

构造函数是 private 润饰,具备一个 static 的部分 instance 变量和一个获取 instance 变量的办法,在获取实例的办法中,先判断是否为空如果是的话就先创立,而后返回结构好的对象。

只是这里有个乏味的中央是,LOCAL 的动态实例变量应用了 ThreadLocal 润饰,也就是说它属于每个线程各自的数据,而在 instance()办法中,先获取本线程的该实例,如果没有就创立该线程独有的 ErrorContext。

4、代理模式

代理模式能够认为是 Mybatis 的外围应用的模式,正是因为这个模式,咱们只须要编写 Mapper.java 接口,不须要实现,由 Mybatis 后盾帮咱们实现具体 SQL 的执行。

代理模式(Proxy Pattern):给某一个对象提供一个代 理,并由代理对象管制对原对象的援用。代理模式的英 文叫做 Proxy 或 Surrogate,它是一种对象结构型模式。

代理模式蕴含如下角色:

  • Subject: 形象主题角色
  • Proxy: 代理主题角色
  • RealSubject: 实在主题角色

这里有两个步骤,第一个是提前创立一个 Proxy,第二个是应用的时候会主动申请 Proxy,而后由 Proxy 来执行具体事务;

当咱们应用 Configuration 的 getMapper 办法时,会调用 mapperRegistry.getMapper 办法,而该办法又会调用 mapperProxyFactory.newInstance(sqlSession)来生成一个具体的代理:

/**
 * @author Lasse Voss
 */
publicclass MapperProxyFactory<T> {

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

    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<T>(sqlSession, mapperInterface, methodCache);
        return newInstance(mapperProxy);
    }

}

在这里,先通过 T newInstance(SqlSession sqlSession)办法会失去一个 MapperProxy 对象,而后调用 T newInstance(MapperProxymapperProxy)生成代理对象而后返回。

而查看 MapperProxy 的代码,能够看到如下内容:

publicclass MapperProxy<T> implements InvocationHandler, Serializable {

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {if (Object.class.equals(method.getDeclaringClass())) {return method.invoke(this, args);
            } elseif (isDefaultMethod(method)) {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.java 接口类,当真正执行一个 Mapper 接口的时候,就会转发给 MapperProxy.invoke 办法,而该办法则会调用后续的 sqlSession.cud>executor.execute>prepareStatement 等一系列办法,实现 SQL 的执行和返回。

5、组合模式

组合模式组合多个对象造成树形构造以示意“整体 - 局部”的构造档次。

组合模式对单个对象 (叶子对象) 和组合对象 (组合对象) 具备一致性,它将对象组织到树结构中,能够用来形容整体与局部的关系。同时它也含糊了简略元素 (叶子对象) 和简单元素 (容器对象) 的概念,使得客户可能像解决简略元素一样来解决简单元素,从而使客户程序可能与简单元素的内部结构解耦。

在应用组合模式中须要留神一点也是组合模式最要害的中央:叶子对象和组合对象实现雷同的接口。这就是组合模式可能将叶子节点和对象节点进行统一解决的起因。

Mybatis 反对动静 SQL 的弱小性能,比方上面的这个 SQL:

<update id="update" parameterType="org.format.dynamicproxy.mybatis.bean.User">
    UPDATE users
    <trim prefix="SET" prefixOverrides=",">
        <if test="name != null and name !=''">
            name = #{name}
        </if>
        <if test="age != null and age !=''">
            , age = #{age}
        </if>
        <if test="birthday != null and birthday !=''">
            , birthday = #{birthday}
        </if>
    </trim>
    where id = ${id}
</update>

在这外面应用到了 trim、if 等动静元素,能够依据条件来生成不同状况下的 SQL;

在 DynamicSqlSource.getBoundSql 办法里,调用了 rootSqlNode.apply(context)办法,apply 办法是所有的动静节点都实现的接口:

publicinterface SqlNode {boolean apply(DynamicContext context);
}

对于实现该 SqlSource 接口的所有节点,就是整个组合模式树的各个节点:

组合模式的简略之处在于,所有的子节点都是同一类节点,能够递归的向下执行,比方对于 TextSqlNode,因为它是最底层的叶子节点,所以间接将对应的内容 append 到 SQL 语句中:

@Override
    public boolean apply(DynamicContext context) {GenericTokenParser parser = createParser(new BindingTokenParser(context, injectionFilter));
        context.appendSql(parser.parse(text));
        returntrue;
    }

然而对于 IfSqlNode,就须要先做判断,如果判断通过,依然会调用子元素的 SqlNode,即 contents.apply 办法,实现递归的解析。

@Override
    public boolean apply(DynamicContext context) {if (evaluator.evaluateBoolean(test, context.getBindings())) {contents.apply(context);
            returntrue;
        }
        returnfalse;
    }

6、模板办法模式

模板办法模式是所有模式中最为常见的几个模式之一,是基于继承的代码复用的根本技术。

模板办法模式须要开发抽象类和具体子类的设计师之间的合作。一个设计师负责给出一个算法的轮廓和骨架,另一些设计师则负责给出这个算法的各个逻辑步骤。代表这些具体逻辑步骤的办法称做根本办法(primitive method);而将这些根本办法汇总起来的办法叫做模板办法(template method),这个设计模式的名字就是从此而来。

模板类定义一个操作中的算法的骨架,而将一些步骤提早到子类中。使得子类能够不扭转一个算法的构造即可重定义该算法的某些特定步骤。

在 Mybatis 中,sqlSession 的 SQL 执行,都是委托给 Executor 实现的,Executor 蕴含以下构造:

其中的 BaseExecutor 就采纳了模板办法模式,它实现了大部分的 SQL 执行逻辑,而后把以下几个办法交给子类定制化实现:

protected abstract int doUpdate(MappedStatement ms, Object parameter) throws SQLException;

    protected abstract List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException;

    protectedabstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds,
            ResultHandler resultHandler, BoundSql boundSql) throws SQLException;

该模板办法类有几个子类的具体实现,应用了不同的策略:

  • 简略 SimpleExecutor:每执行一次 update 或 select,就开启一个 Statement 对象,用完立即敞开 Statement 对象。(能够是 Statement 或 PrepareStatement 对象)
  • 重用 ReuseExecutor:执行 update 或 select,以 sql 作为 key 查找 Statement 对象,存在就应用,不存在就创立,用完后,不敞开 Statement 对象,而是搁置于 Map<String, Statement> 内,供下一次应用。(能够是 Statement 或 PrepareStatement 对象)
  • 批量 BatchExecutor:执行 update(没有 select,JDBC 批处理不反对 select),将所有 sql 都增加到批处理中(addBatch()),期待对立执行(executeBatch()),它缓存了多个 Statement 对象,每个 Statement 对象都是 addBatch()结束后,期待逐个执行 executeBatch()批处理的;BatchExecutor 相当于保护了多个桶,每个桶里都装了很多属于本人的 SQL,就像苹果蓝里装了很多苹果,番茄蓝里装了很多番茄,最初,再对立倒进仓库。(能够是 Statement 或 PrepareStatement 对象)

比方在 SimpleExecutor 中这样实现 update 办法:

@Override
    public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
        Statement stmt = null;
        try {Configuration configuration = ms.getConfiguration();
            StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null,
                    null);
            stmt = prepareStatement(handler, ms.getStatementLog());
            return handler.update(stmt);
        } finally {closeStatement(stmt);
        }
    }

7、适配器模式

适配器模式(Adapter Pattern):将一个接口转换成客户心愿的另一个接口,适配器模式使接口不兼容的那些类能够一起工作,其别名为包装器(Wrapper)。适配器模式既能够作为类结构型模式,也能够作为对象结构型模式。

在 Mybatsi 的 logging 包中,有一个 Log 接口:

/**
 * @author Clinton Begin
 */
publicinterface 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);

}

该接口定义了 Mybatis 间接应用的日志办法,而 Log 接口具体由谁来实现呢?Mybatis 提供了多种日志框架的实现,这些实现都匹配这个 Log 接口所定义的接口办法,最终实现了所有内部日志框架到 Mybatis 日志包的适配:

比方对于 Log4jImpl 的实现来说,该实现持有了 org.apache.log4j.Logger 的实例,而后所有的日志办法,均委托该实例来实现。

publicclass Log4jImpl implements Log {privatestaticfinal String FQCN = Log4jImpl.class.getName();

    private Logger log;

    public Log4jImpl(String clazz) {log = Logger.getLogger(clazz);
    }

    @Override
    public boolean isDebugEnabled() {return log.isDebugEnabled();
    }

    @Override
    public boolean isTraceEnabled() {return log.isTraceEnabled();
    }

    @Override
    public void error(String s, Throwable e) {log.log(FQCN, Level.ERROR, s, e);
    }

    @Override
    public void error(String s) {log.log(FQCN, Level.ERROR, s, null);
    }

    @Override
    public void debug(String s) {log.log(FQCN, Level.DEBUG, s, null);
    }

    @Override
    public void trace(String s) {log.log(FQCN, Level.TRACE, s, null);
    }

    @Override
    public void warn(String s) {log.log(FQCN, Level.WARN, s, null);
    }

}

8、装璜者模式

装璜模式(Decorator Pattern):动静地给一个对象减少一些额定的职责(Responsibility),就减少对象性能来说,装璜模式比生成子类实现更为灵便。其别名也能够称为包装器(Wrapper),与适配器模式的别名雷同,但它们实用于不同的场合。依据翻译的不同,装璜模式也有人称之为“油漆工模式”,它是一种对象结构型模式。

在 mybatis 中,缓存的性能由根接口 Cache(org.apache.ibatis.cache.Cache)定义。整个体系采纳装璜器设计模式,数据存储和缓存的基本功能由 PerpetualCache(org.apache.ibatis.cache.impl.PerpetualCache)永恒缓存实现,而后通过一系列的装璜器来对 PerpetualCache 永恒缓存进行缓存策略等不便的管制。如下图:

用于装璜 PerpetualCache 的规范装璜器共有 8 个(全副在 org.apache.ibatis.cache.decorators 包中):

  1. FifoCache:先进先出算法,缓存回收策略
  2. LoggingCache:输入缓存命中的日志信息
  3. LruCache:最近起码应用算法,缓存回收策略
  4. ScheduledCache:调度缓存,负责定时清空缓存
  5. SerializedCache:缓存序列化和反序列化存储
  6. SoftCache:基于软援用实现的缓存管理策略
  7. SynchronizedCache:同步的缓存装璜器,用于避免多线程并发拜访
  8. WeakCache:基于弱援用实现的缓存管理策略

另外,还有一个非凡的装璜器 TransactionalCache:事务性的缓存

正如大多数长久层框架一样,mybatis 缓存同样分为一级缓存和二级缓存

  • 一级缓存,又叫本地缓存,是 PerpetualCache 类型的永恒缓存,保留在执行器中(BaseExecutor),而执行器又在 SqlSession(DefaultSqlSession)中,所以一级缓存的生命周期与 SqlSession 是雷同的。
  • 二级缓存,又叫自定义缓存,实现了 Cache 接口的类都能够作为二级缓存,所以可配置如 encache 等的第三方缓存。二级缓存以 namespace 名称空间为其惟一标识,被保留在 Configuration 外围配置对象中。

二级缓存对象的默认类型为 PerpetualCache,如果配置的缓存是默认类型,则 mybatis 会依据配置主动追加一系列装璜器。

Cache 对象之间的援用程序为:

SynchronizedCache–>LoggingCache–>SerializedCache–>ScheduledCache–>LruCache–>PerpetualCache

9、迭代器模式

迭代器(Iterator)模式,又叫做游标(Cursor)模式。GOF 给出的定义为:提供一种办法拜访一个容器(container)对象中各个元素,而又不需裸露该对象的外部细节。

Java 的 Iterator 就是迭代器模式的接口,只有实现了该接口,就相当于利用了迭代器模式:

比方 Mybatis 的 PropertyTokenizer 是 property 包中的重量级类,该类会被 reflection 包中其余的类频繁的援用到。这个类实现了 Iterator 接口,在应用时常常被用到的是 Iterator 接口中的 hasNext 这个函数。

publicclass PropertyTokenizer implements Iterator<PropertyTokenizer> {
    private String name;
    private String indexedName;
    private String index;
    private String children;

    public PropertyTokenizer(String fullname) {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);
        }
    }

    public String getName() {return name;}

    public String getIndex() {return index;}

    public String getIndexedName() {return indexedName;}

    public String getChildren() {return children;}

    @Override
    public boolean hasNext() {return children != null;}

    @Override
    public PropertyTokenizer next() {returnnew PropertyTokenizer(children);
    }

    @Override
    public void remove() {
        thrownew UnsupportedOperationException("Remove is not supported, as it has no meaning in the context of properties.");
    }
}

能够看到,这个类传入一个字符串到构造函数,而后提供了 iterator 办法对解析后的子串进行遍历,是一个很罕用的办法类。

正文完
 0