php双语网站,网站内部搜索怎么做,页面模板如何设置,手机wap网站特效戳蓝字“CSDN云计算”关注我们哦#xff01;作者 | 疯狂的蚂蚁来源 | https://dwz.cn/KFgol1De之前总结过一篇Spring中用到了哪些设计模式#xff1a;《面试官:“谈谈Spring中都用到了那些设计模式?”》#xff0c;昨晚看到了一篇很不错的一篇介绍MyBatis中都用到了那些设计… 戳蓝字“CSDN云计算”关注我们哦作者 | 疯狂的蚂蚁来源 | https://dwz.cn/KFgol1De之前总结过一篇Spring中用到了哪些设计模式《面试官:“谈谈Spring中都用到了那些设计模式?”》昨晚看到了一篇很不错的一篇介绍MyBatis中都用到了那些设计模式的文章今天分享给各位。虽然我们都知道有26个设计模式但是大多停留在概念层面真实开发中很少遇到Mybatis源码中使用了大量的设计模式阅读源码并观察设计模式在其中的应用能够更深入的理解设计模式。Mybatis至少遇到了以下的设计模式的使用Builder模式 :例如 SqlSessionFactoryBuilder、XMLConfigBuilder、XMLMapperBuilder、XMLStatementBuilder、CacheBuilder工厂模式 :例如SqlSessionFactory、ObjectFactory、MapperProxyFactory单例模式 :例如ErrorContext和LogFactory代理模式 :Mybatis实现的核心比如MapperProxy、ConnectionLogger用的jdk的动态代理还有executor.loader包使用了cglib或者javassist达到延迟加载的效果组合模式 :例如SqlNode和各个子类ChooseSqlNode等模板方法模式 : 例如BaseExecutor和SimpleExecutor还有BaseTypeHandler和所有的子类例如IntegerTypeHandler适配器模式 : 例如Log的Mybatis接口和它对jdbc、log4j等各种日志框架的适配实现装饰者模式 : 例如cache包中的cache.decorators子包中等各个装饰者的实现迭代器模式 : 例如迭代器模式PropertyTokenizer接下来挨个模式进行解读先介绍模式自身的知识然后解读在Mybatis中怎样应用了该模式。1、Builder 模式Builder模式的定义是“将一个复杂对象的构建与它的表示分离使得同样的构建过程可以创建不同的表示。”它属于创建类模式一般来说如果一个对象的构建比较复杂超出了构造函数所能包含的范围就可以使用工厂模式和Builder模式相对于工厂模式会产出一个完整的产品Builder应用于更加复杂的对象的构建甚至只会构建产品的一个部分。《effective-java》中第2条也提到遇到多个构造器参数时考虑用构建者(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为例它包含以下方法SqlSessionFactoryBuilder即根据不同的输入参数来构建SqlSessionFactory这个工厂对象。2、工厂模式在Mybatis中比如SqlSessionFactory使用的是工厂模式该工厂没有那么复杂的逻辑是一个简单工厂模式。简单工厂模式(Simple Factory Pattern)又称为静态工厂方法(Static Factory Method)模式它属于类创建型模式。在简单工厂模式中可以根据参数的不同返回不同类的实例。简单工厂模式专门定义一个类来负责创建其他类的实例被创建的实例通常都具有共同的父类。简单工厂模式SqlSession可以认为是一个Mybatis工作的核心的接口通过这个接口可以执行执行SQL语句、获取Mappers、管理事务。类似于连接MySQL的Connection对象。SqlSessionFactory可以看到该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); 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(); } } 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); 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。在这里其实也可以看到端倪SqlSession的执行其实是委托给对应的Executor来进行的。而对于LogFactory它的实现代码public final class LogFactory { private static Constructor? extends Log logConstructor; private LogFactory() { // disable construction } public static Log getLog(Class? aClass) { return getLog(aClass.getName()); }final class LogFactory { private static Constructor? extends Log logConstructor; private LogFactory() { // disable construction } public static Log getLog(Class? aClass) { return getLog(aClass.getName()); }这里有个特别的地方是Log变量的的类型是Constructor? extendsLog也就是说该工厂生产的不只是一个产品而是具有Log公共接口的一系列产品比如Log4jImpl、Slf4jImpl等很多具体的Log。3、单例模式单例模式(Singleton Pattern)单例模式确保某一个类只有一个实例而且自行实例化并向整个系统提供这个实例这个类称为单例类它提供全局访问的方法。单例模式的要点有三个一是某个类只能有一个实例二是它必须自行创建这个实例三是它必须自行向整个系统提供这个实例。单例模式是一种对象创建型模式。单例模式又名单件模式或单态模式。单例模式在Mybatis中有两个地方用到单例模式ErrorContext和LogFactory其中ErrorContext是用在每个线程范围内的单例用于记录该线程的执行环境错误信息而LogFactory则是提供给整个Mybatis使用的日志工厂用于获得针对项目配置好的日志对象。ErrorContext的单例实现代码public class ErrorContext { private static final ThreadLocalErrorContext LOCAL new ThreadLocalErrorContext(); private ErrorContext() { } public static ErrorContext instance() { ErrorContext context LOCAL.get(); if (context null) { context new ErrorContext(); LOCAL.set(context); } return context; }class ErrorContext { private static final ThreadLocalErrorContext LOCAL new ThreadLocalErrorContext(); 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它是一种对象结构型模式。代理模式包含如下角色代理模式这里有两个步骤第一个是提前创建一个Proxy第二个是使用的时候会自动请求Proxy然后由Proxy来执行具体事务当我们使用Configuration的getMapper方法时会调用mapperRegistry.getMapper方法而该方法又会调用mapperProxyFactory.newInstance(sqlSession)来生成一个具体的代理/** * author Lasse Voss */public class MapperProxyFactoryT { private final ClassT mapperInterface; private final MapMethod, MapperMethod methodCache new ConcurrentHashMapMethod, MapperMethod(); public MapperProxyFactory(ClassT mapperInterface) { this.mapperInterface mapperInterface; } public ClassT getMapperInterface() { return mapperInterface; } public MapMethod, MapperMethod getMethodCache() { return methodCache; } SuppressWarnings(unchecked) protected T newInstance(MapperProxyT mapperProxy) { return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); } public T newInstance(SqlSession sqlSession) { final MapperProxyT mapperProxy new MapperProxyT(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); }}public class MapperProxyFactoryT { private final ClassT mapperInterface; private final MapMethod, MapperMethod methodCache new ConcurrentHashMapMethod, MapperMethod(); public MapperProxyFactory(ClassT mapperInterface) { this.mapperInterface mapperInterface; } public ClassT getMapperInterface() { return mapperInterface; } public MapMethod, MapperMethod getMethodCache() { return methodCache; } SuppressWarnings(unchecked) protected T newInstance(MapperProxyT mapperProxy) { return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); } public T newInstance(SqlSession sqlSession) { final MapperProxyT mapperProxy new MapperProxyT(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); }}在这里先通过T newInstance(SqlSession sqlSession)方法会得到一个MapperProxy对象然后调用T newInstance(MapperProxyT mapperProxy)生成代理对象然后返回。而查看MapperProxy的代码可以看到如下内容public class MapperProxyT 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); } else if (isDefaultMethod(method)) { return invokeDefaultMethod(proxy, method, args); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } final MapperMethod mapperMethod cachedMapperMethod(method); return mapperMethod.execute(sqlSession, args); }class MapperProxyT 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); } else if (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.cudexecutor.executeprepareStatement等一系列方法完成SQL的执行和返回。5、组合模式组合模式组合多个对象形成树形结构以表示“整体-部分”的结构层次。组合模式对单个对象(叶子对象)和组合对象(组合对象)具有一致性它将对象组织到树结构中可以用来描述整体与部分的关系。同时它也模糊了简单元素(叶子对象)和复杂元素(容器对象)的概念使得客户能够像处理简单元素一样来处理复杂元素从而使客户程序能够与复杂元素的内部结构解耦。在使用组合模式中需要注意一点也是组合模式最关键的地方叶子对象和组合对象实现相同的接口。这就是组合模式能够将叶子节点和对象节点进行一致处理的原因。组合模式Mybatis支持动态SQL的强大功能比如下面的这个SQLupdate idupdate parameterTypeorg.format.dynamicproxy.mybatis.bean.User UPDATE users trim prefixSET prefixOverrides, if testname ! null and name ! name #{name} /if if testage ! null and age ! , age #{age} /if if testbirthday ! null and birthday ! , birthday #{birthday} /if /trim where id ${id}/update UPDATE users trim prefixSET prefixOverrides, if testname ! null and name ! name #{name} /if if testage ! null and age ! , age #{age} /if if testbirthday ! null and birthday ! , birthday #{birthday} /if /trim where id ${id}/update在这里面使用到了trim、if等动态元素可以根据条件来生成不同情况下的SQL在DynamicSqlSource.getBoundSql方法里调用了rootSqlNode.apply(context)方法apply方法是所有的动态节点都实现的接口public interface SqlNode { boolean apply(DynamicContext context);}interface SqlNode { boolean apply(DynamicContext context);}对于实现该SqlSource接口的所有节点就是整个组合模式树的各个节点SqlNode组合模式的简单之处在于所有的子节点都是同一类节点可以递归的向下执行比如对于TextSqlNode因为它是最底层的叶子节点所以直接将对应的内容append到SQL语句中 Override public boolean apply(DynamicContext context) { GenericTokenParser parser createParser(new BindingTokenParser(context, injectionFilter)); context.appendSql(parser.parse(text)); return true; } public boolean apply(DynamicContext context) { GenericTokenParser parser createParser(new BindingTokenParser(context, injectionFilter)); context.appendSql(parser.parse(text)); return true; }但是对于IfSqlNode就需要先做判断如果判断通过仍然会调用子元素的SqlNode即contents.apply方法实现递归的解析。Overridepublic boolean apply(DynamicContext context) { if (evaluator.evaluateBoolean(test, context.getBindings())) { contents.apply(context); return true; } return false;} public boolean apply(DynamicContext context) { if (evaluator.evaluateBoolean(test, context.getBindings())) { contents.apply(context); return true; } return false;} 6、模板方法模式模板方法模式是所有模式中最为常见的几个模式之一是基于继承的代码复用的基本技术。模板方法模式需要开发抽象类和具体子类的设计师之间的协作。一个设计师负责给出一个算法的轮廓和骨架另一些设计师则负责给出这个算法的各个逻辑步骤。代表这些具体逻辑步骤的方法称做基本方法(primitive method)而将这些基本方法汇总起来的方法叫做模板方法(template method)这个设计模式的名字就是从此而来。模板类定义一个操作中的算法的骨架而将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。模板方法模式在Mybatis中sqlSession的SQL执行都是委托给Executor实现的Executor包含以下结构Executor接口其中的BaseExecutor就采用了模板方法模式它实现了大部分的SQL执行逻辑然后把以下几个方法交给子类定制化完成 Override public boolean apply(DynamicContext context) { if (evaluator.evaluateBoolean(test, context.getBindings())) { contents.apply(context); return true; } return false; } public boolean apply(DynamicContext context) { if (evaluator.evaluateBoolean(test, context.getBindings())) { contents.apply(context); return true; } return false; }该模板方法类有几个子类的具体实现使用了不同的策略比如在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); } } 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 */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);}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);}该接口定义了Mybatis直接使用的日志方法而Log接口具体由谁来实现呢Mybatis提供了多种日志框架的实现这些实现都匹配这个Log接口所定义的接口方法最终实现了所有外部日志框架到Mybatis日志包的适配Log比如对于Log4jImpl的实现来说该实现持有了org.apache.log4j.Logger的实例然后所有的日志方法均委托该实例来实现。public class Log4jImpl implements Log { private static final 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); }}class Log4jImpl implements Log { private static final 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中缓存的功能由根接口Cacheorg.apache.ibatis.cache.Cache定义。整个体系采用装饰器设计模式数据存储和缓存的基本功能由PerpetualCacheorg.apache.ibatis.cache.impl.PerpetualCache永久缓存实现然后通过一系列的装饰器来对PerpetualCache永久缓存进行缓存策略等方便的控制。如下图Cache用于装饰PerpetualCache的标准装饰器共有8个全部在org.apache.ibatis.cache.decorators包中FifoCache先进先出算法缓存回收策略LoggingCache输出缓存命中的日志信息LruCache最近最少使用算法缓存回收策略ScheduledCache调度缓存负责定时清空缓存SerializedCache缓存序列化和反序列化存储SoftCache基于软引用实现的缓存管理策略SynchronizedCache同步的缓存装饰器用于防止多线程并发访问WeakCache基于弱引用实现的缓存管理策略另外还有一个特殊的装饰器TransactionalCache事务性的缓存正如大多数持久层框架一样mybatis缓存同样分为一级缓存和二级缓存二级缓存对象的默认类型为PerpetualCache如果配置的缓存是默认类型则mybatis会根据配置自动追加一系列装饰器。Cache对象之间的引用顺序为SynchronizedCache–LoggingCache–SerializedCache–ScheduledCache–LruCache–PerpetualCache9、迭代器模式迭代器Iterator模式又叫做游标Cursor模式。GOF给出的定义为提供一种方法访问一个容器container对象中各个元素而又不需暴露该对象的内部细节。迭代器模式Java的Iterator就是迭代器模式的接口只要实现了该接口就相当于应用了迭代器模式Iterator比如Mybatis的PropertyTokenizer是property包中的重量级类该类会被reflection包中其他的类频繁的引用到。这个类实现了Iterator接口在使用时经常被用到的是Iterator接口中的hasNext这个函数。public class PropertyTokenizer implements IteratorPropertyTokenizer { 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() { return new PropertyTokenizer(children); } Override public void remove() { throw new UnsupportedOperationException( Remove is not supported, as it has no meaning in the context of properties.); }}class PropertyTokenizer implements IteratorPropertyTokenizer { 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() { return new PropertyTokenizer(children); } Override public void remove() { throw new UnsupportedOperationException( Remove is not supported, as it has no meaning in the context of properties.); }}可以看到这个类传入一个字符串到构造函数然后提供了iterator方法对解析后的子串进行遍历是一个很常用的方法类。参考资料•图说设计模式http://design-patterns.readthedocs.io/zh_CN/latest/index.html•深入浅出Mybatis系列十—SQL执行流程分析源码篇http://www.cnblogs.com/dongying/p/4142476.html•设计模式读书笔记—–组合模式 http://www.cnblogs.com/chenssy/p/3299719.html•Mybatis3.3.x技术内幕四五鼠闹东京之执行器Executor设计原本 http://blog.csdn.net/wagcy/article/details/32963235•mybatis缓存机制详解一——Cache https://my.oschina.net/lixin91/blog/620068福利扫描添加小编微信备注“姓名公司职位”加入【云计算学习交流群】和志同道合的朋友们共同打卡学习推荐阅读听说私有云也出新一代了搞不懂SDN那是因为你没看这个小故事…华为最强自研 NPU 问世麒麟 810 “抛弃”寒武纪北邮通信博士万字长文带你秒懂 4G/5G 区别LinkedIn最新报告: 区块链成职位需求增长最快领域, 这些地区对区块链人才渴求度最高……中文NLP的分词真有必要吗李纪为团队四项任务评测一探究竟 | ACL 20196月技术福利限时免费领真香朕在看了