• 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办法对解析后的子串进行遍历,是一个很罕用的办法类。