(给ImportNew加星标,提高Java技能)
作者:crazyant
www.crazyant.net/2022.html
虽然我们都知道有26个设计模式,但是大多停留在概念层面,真实开发中很少遇到,Mybatis源码中使用了大量的设计模式,阅读源码并观察设计模式在其中的应用,能够更深入的理解设计模式。
Mybatis至少遇到了以下的设计模式的使用:
接下来挨个模式进行解读,先介绍模式自身的知识,然后解读在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使用的是工厂模式,该工厂没有那么复杂的逻辑,是一个简单工厂模式。
简单工厂模式(SimpleFactoryPattern):又称为静态工厂方法(StaticFactoryMethod)模式,它属于类创建型模式。在简单工厂模式中,可以根据参数的不同返回不同类的实例。简单工厂模式专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。
SqlSession可以认为是一个Mybatis工作的核心的接口,通过这个接口可以执行执行SQL语句、获取Mappers、管理事务。类似于连接MySQL的Connection对象。
可以看到,该Factory的openSession方法重载了很多个,分别支持autoCommit、Executor、Transaction等参数的输入,来构建核心的SqlSession对象。
在DefaultSqlSessionFactory的默认工厂实现里,有一个方法可以看出工厂怎么产出一个产品:
privateSqlSessionopenSessionFromDataSource(ExecutorTypeexecType,TransactionIsolationLevellevel,booleanautoCommit){Transactiontx=null;try{finalEnvironmentenvironment=configuration.getEnvironment();finalTransactionFactorytransactionFactory=getTransactionFactoryFromEnvironment(environment);tx=transactionFactory.newTransaction(environment.getDataSource(),level,autoCommit);finalExecutorexecutor=configuration.newExecutor(tx,execType);returnnewDefaultSqlSession(configuration,executor,autoCommit);}catch(Exceptione){closeTransaction(tx);//mayhavefetchedaconnectionsoletscall//close()throwExceptionFactory.wrapException("Erroropeningsession.Cause:"+e,e);}finally{ErrorContext.instance().reset();}}
这是一个openSession调用的底层方法,该方法先从configuration读取对应的环境配置,然后初始化TransactionFactory获得一个Transaction对象,然后通过Transaction获取一个Executor对象,最后通过configuration、Executor、是否autoCommit三个参数构建了SqlSession。
在这里其实也可以看到端倪,SqlSession的执行,其实是委托给对应的Executor来进行的。
而对于LogFactory,它的实现代码:
publicfinalclassLogFactory{privatestaticConstructor
这里有个特别的地方,是Log变量的的类型是Constructor
3、单例模式
单例模式(SingletonPattern):单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。
单例模式的要点有三个:一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例。单例模式是一种对象创建型模式。单例模式又名单件模式或单态模式。
在Mybatis中有两个地方用到单例模式,ErrorContext和LogFactory,其中ErrorContext是用在每个线程范围内的单例,用于记录该线程的执行环境错误信息,而LogFactory则是提供给整个Mybatis使用的日志工厂,用于获得针对项目配置好的日志对象。
ErrorContext的单例实现代码:
publicclassErrorContext{privatestaticfinalThreadLocal
构造函数是private修饰,具有一个static的局部instance变量和一个获取instance变量的方法,在获取实例的方法中,先判断是否为空如果是的话就先创建,然后返回构造好的对象。
只是这里有个有趣的地方是,LOCAL的静态实例变量使用了ThreadLocal修饰,也就是说它属于每个线程各自的数据,而在instance()方法中,先获取本线程的该实例,如果没有就创建该线程独有的ErrorContext。
4、代理模式
代理模式可以认为是Mybatis的核心使用的模式,正是由于这个模式,我们只需要编写Mapper.java接口,不需要实现,由Mybatis后台帮我们完成具体SQL的执行。
代理模式(ProxyPattern):给某一个对象提供一个代理,并由代理对象控制对原对象的引用。代理模式的英文叫做Proxy或Surrogate,它是一种对象结构型模式。
代理模式包含如下角色:
这里有两个步骤,第一个是提前创建一个Proxy,第二个是使用的时候会自动请求Proxy,然后由Proxy来执行具体事务;
当我们使用Configuration的getMapper方法时,会调用mapperRegistry.getMapper方法,而该方法又会调用mapperProxyFactory.newInstance(sqlSession)来生成一个具体的代理:
/***@authorLasseVoss*/publicclassMapperProxyFactory
在这里,先通过TnewInstance(SqlSessionsqlSession)方法会得到一个MapperProxy对象,然后调用TnewInstance(MapperProxy
而查看MapperProxy的代码,可以看到如下内容:
publicclassMapperProxy
非常典型的,该MapperProxy类实现了InvocationHandler接口,并且实现了该接口的invoke方法。
通过这种方式,我们只需要编写Mapper.java接口类,当真正执行一个Mapper接口的时候,就会转发给MapperProxy.invoke方法,而该方法则会调用后续的sqlSession.cud>executor.execute>prepareStatement等一系列方法,完成SQL的执行和返回。
5、组合模式
组合模式组合多个对象形成树形结构以表示“整体-部分”的结构层次。
组合模式对单个对象(叶子对象)和组合对象(组合对象)具有一致性,它将对象组织到树结构中,可以用来描述整体与部分的关系。同时它也模糊了简单元素(叶子对象)和复杂元素(容器对象)的概念,使得客户能够像处理简单元素一样来处理复杂元素,从而使客户程序能够与复杂元素的内部结构解耦。
在使用组合模式中需要注意一点也是组合模式最关键的地方:叶子对象和组合对象实现相同的接口。这就是组合模式能够将叶子节点和对象节点进行一致处理的原因。
Mybatis支持动态SQL的强大功能,比如下面的这个SQL:
在这里面使用到了trim、if等动态元素,可以根据条件来生成不同情况下的SQL;
在DynamicSqlSource.getBoundSql方法里,调用了rootSqlNode.apply(context)方法,apply方法是所有的动态节点都实现的接口:
publicinterfaceSqlNode{booleanapply(DynamicContextcontext);}
对于实现该SqlSource接口的所有节点,就是整个组合模式树的各个节点:
组合模式的简单之处在于,所有的子节点都是同一类节点,可以递归的向下执行,比如对于TextSqlNode,因为它是最底层的叶子节点,所以直接将对应的内容append到SQL语句中:
@Overridepublicbooleanapply(DynamicContextcontext){GenericTokenParserparser=createParser(newBindingTokenParser(context,injectionFilter));context.appendSql(parser.parse(text));returntrue;}
但是对于IfSqlNode,就需要先做判断,如果判断通过,仍然会调用子元素的SqlNode,即contents.apply方法,实现递归的解析。
@Overridepublicbooleanapply(DynamicContextcontext){if(evaluator.evaluateBoolean(test,context.getBindings())){contents.apply(context);returntrue;}returnfalse;}
6、模板方法模式
模板方法模式是所有模式中最为常见的几个模式之一,是基于继承的代码复用的基本技术。
模板方法模式需要开发抽象类和具体子类的设计师之间的协作。一个设计师负责给出一个算法的轮廓和骨架,另一些设计师则负责给出这个算法的各个逻辑步骤。代表这些具体逻辑步骤的方法称做基本方法(primitivemethod);而将这些基本方法汇总起来的方法叫做模板方法(templatemethod),这个设计模式的名字就是从此而来。
模板类定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
在Mybatis中,sqlSession的SQL执行,都是委托给Executor实现的,Executor包含以下结构:
其中的BaseExecutor就采用了模板方法模式,它实现了大部分的SQL执行逻辑,然后把以下几个方法交给子类定制化完成:
protectedabstractintdoUpdate(MappedStatementms,Objectparameter)throwsSQLException;protectedabstractList
该模板方法类有几个子类的具体实现,使用了不同的策略:
比如在SimpleExecutor中这样实现update方法:
@OverridepublicintdoUpdate(MappedStatementms,Objectparameter)throwsSQLException{Statementstmt=null;try{Configurationconfiguration=ms.getConfiguration();StatementHandlerhandler=configuration.newStatementHandler(this,ms,parameter,RowBounds.DEFAULT,null,null);stmt=prepareStatement(handler,ms.getStatementLog());returnhandler.update(stmt);}finally{closeStatement(stmt);}}
7、适配器模式
适配器模式(AdapterPattern):将一个接口转换成客户希望的另一个接口,适配器模式使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper)。适配器模式既可以作为类结构型模式,也可以作为对象结构型模式。
在Mybatsi的logging包中,有一个Log接口:
/***@authorClintonBegin*/publicinterfaceLog{booleanisDebugEnabled();booleanisTraceEnabled();voiderror(Strings,Throwablee);voiderror(Strings);voiddebug(Strings);voidtrace(Strings);voidwarn(Strings);}
该接口定义了Mybatis直接使用的日志方法,而Log接口具体由谁来实现呢?Mybatis提供了多种日志框架的实现,这些实现都匹配这个Log接口所定义的接口方法,最终实现了所有外部日志框架到Mybatis日志包的适配:
比如对于Log4jImpl的实现来说,该实现持有了org.apache.log4j.Logger的实例,然后所有的日志方法,均委托该实例来实现。
publicclassLog4jImplimplementsLog{privatestaticfinalStringFQCN=Log4jImpl.class.getName();privateLoggerlog;publicLog4jImpl(Stringclazz){log=Logger.getLogger(clazz);}@OverridepublicbooleanisDebugEnabled(){returnlog.isDebugEnabled();}@OverridepublicbooleanisTraceEnabled(){returnlog.isTraceEnabled();}@Overridepublicvoiderror(Strings,Throwablee){log.log(FQCN,Level.ERROR,s,e);}@Overridepublicvoiderror(Strings){log.log(FQCN,Level.ERROR,s,null);}@Overridepublicvoiddebug(Strings){log.log(FQCN,Level.DEBUG,s,null);}@Overridepublicvoidtrace(Strings){log.log(FQCN,Level.TRACE,s,null);}@Overridepublicvoidwarn(Strings){log.log(FQCN,Level.WARN,s,null);}}
8、装饰者模式
装饰模式(DecoratorPattern):动态地给一个对象增加一些额外的职责(Responsibility),就增加对象功能来说,装饰模式比生成子类实现更为灵活。其别名也可以称为包装器(Wrapper),与适配器模式的别名相同,但它们适用于不同的场合。根据翻译的不同,装饰模式也有人称之为“油漆工模式”,它是一种对象结构型模式。
在mybatis中,缓存的功能由根接口Cache(org.apache.ibatis.cache.Cache)定义。整个体系采用装饰器设计模式,数据存储和缓存的基本功能由PerpetualCache(org.apache.ibatis.cache.impl.PerpetualCache)永久缓存实现,然后通过一系列的装饰器来对PerpetualCache永久缓存进行缓存策略等方便的控制。如下图:
用于装饰PerpetualCache的标准装饰器共有8个(全部在org.apache.ibatis.cache.decorators包中):
另外,还有一个特殊的装饰器TransactionalCache:事务性的缓存
正如大多数持久层框架一样,mybatis缓存同样分为一级缓存和二级缓存
二级缓存对象的默认类型为PerpetualCache,如果配置的缓存是默认类型,则mybatis会根据配置自动追加一系列装饰器。
Cache对象之间的引用顺序为:
SynchronizedCache–>LoggingCache–>SerializedCache–>ScheduledCache–>LruCache–>PerpetualCache
9、迭代器模式
迭代器(Iterator)模式,又叫做游标(Cursor)模式。GOF给出的定义为:提供一种方法访问一个容器(container)对象中各个元素,而又不需暴露该对象的内部细节。
Java的Iterator就是迭代器模式的接口,只要实现了该接口,就相当于应用了迭代器模式:
比如Mybatis的PropertyTokenizer是property包中的重量级类,该类会被reflection包中其他的类频繁的引用到。这个类实现了Iterator接口,在使用时经常被用到的是Iterator接口中的hasNext这个函数。
publicclassPropertyTokenizerimplementsIterator
可以看到,这个类传入一个字符串到构造函数,然后提供了iterator方法对解析后的子串进行遍历,是一个很常用的方法类。
参考资料:
首先spring,相信大家都很熟悉了。
1、轻量级零配置,API使用简单
2、面向Bean只需要编写普通的Bean(一个Bean代表一个对象)
3、松耦合充分利用AOP思想)(各自可以独立开发,然后整合起来运行)
4、万能胶与主流框架无缝集成(Mybatisdubbo等等)
5、设计模式将Java中经典的设计模式运用的淋漓尽致
Spring解决企业级应用开发的负责设计,简化开发。
1,基于POJO的清理爱你国际和最小侵入性(代码的嵌套,独自开发合起来运行)
2,通过依赖注入和面向接口松耦合
4、通过切面和模板减少版式代码
主要通过,面向Bean、依赖注入以及面向切面三种方式达成
Spring提供了IOC容器,通过配置文件或者注解的方式来管理对象之间的依赖关系
Aa=newA()//实例化后用一个变量保存下来(匿名对象)------------------》Spring用IOC容器存储起来~
a.c()//必须初始化才运行----------------------->Spring帮忙初始化,实例化(控制器给了spring)
最终实现了依赖注入:
@autowriteInterfaAa//自动吧他的实现类注入进来
@Resource(“aa”)//IOC容器种类的id为“aa”的对象自动注入到这里
@autowriteAa//根据类型自动注入
Spring的注入方式
1、setter
2、构造方法
3、强制赋值
面向切面,AOP核心思想--解耦!把一个整体拆开,分别开发,发布时候,再组装到一起运行,切面就是规则!
比如事务;
开启事务执行事务事务回滚关闭事务这就是规则!!!!!!
这种有规律的,就可以认为他是固定的,可以单独拿出来开发设计,作为一个模块(比如日志啊)。
AOP就是个编程思想而已
关于Spring的使用,特点,网上资料很多,大家可以自己找找学习下。本博客主要对于源码进行解读。
AOP的功能完全集成到了Spring事务管理、日志和其他各种特性的上下文中
authentication权限认证
Logging日志
TransctionsManager事务
LazyLoading懒加载
ContexProcess上下文处理
ErrorHandler错误跟踪(异常捕获机制)
Cache缓存
1、除了AOP以外的设计模式
a、代理模式
b、工厂模式
c、单例模式
d、委派模式
e、策略模式
f、策略模式
g、原型模式
代理模式原理:
1、拿到被代理对象的引用,然后获取它的接口
2、JDK代理重新生成一个类,同时实现我们给的代理对象所实现的接口
3、把代理对象的引用拿到
4、重新动态生成一个class字节码
5、编译
动态代理调用哪个方法就代理哪个方法
整个类生成一个新的类
大家认真仔细研究好代理模式,代理模式在Spring中应用非常广泛!!!
JDK代理模式实现:
1、定义接口
2、定义实现接口的类
3、代理类,代理类需要实现InvocationHandler接口,然后实现invoke方法
实现这个接口
不用jdk的任何东西!
首先规定有个InvocationHandler有个invoke方法
importjava.lang.reflect.Method;publicinterfaceGPInvocationHandler{publicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable;}实现个Proxy里面有InvocationHandler引用有newInstance的方法classloader方法
JDK必须实现接口!!!!
满足代理模式应用场景的三个必要条件,
1、需要有两个角色执行者和被代理对象
2、注重过程,必须要做,被代理对象不做
3、执行者必须拿到被代理对象的资料(执行者持有被代理对象的引用)
代理模式总结到底层就是:字节码重组!(字节码重组时候对象要强制转换,必须要实现一个接口)
Java源代码--->编译---->字节码(在原始的加了东西)-->加载到jvm中
然后cglib不需要,Spring主要用的cglib做动态代理定义一个类自动生成一个类自动继承这个类子类引用指向父类看下面:
(同样做了字节码重组事情)
是继承关系
代理可以实现在每一个方法调用之前加一些代码,方法嗲用之后加一些代码
AOP:事务代理、日志监听
Service方法开启一个事务事务的执行是由我们自己的代码完成的
1、监听到是否有异常,可能需要根据异常的类型来决定这个事务是否好回滚or继续提交?(commitorrollback?)
2、事务要关闭掉
通过动态代理给加了代码
-------------------------------------工厂模式
首先要区分生产者消费者消费者不关心工厂、过程只关心结果从工厂取东西哈哈
简单工厂:
定义接口:
定义工厂接口
实现不同工厂
消费者使用
1、定义工厂接口
这个不再是接口了而是抽象类
抽象类可以引用自己的方法!
默认的方法
单例模式:
整个系统从启动到终止,自会有一个实例
在应用中遇到功能性冲突的时候,需要用到单例模式
单例模式有7种写法!!!
1.
3、
委派模式:
1类似中介的功能(委托机制)
2只有被委托人的引用
两个角色受托人委托人
定义一个接口
关于策略模式,参考系Comparator方法就可以啦返回-101这种的
a、比较器接口
b、调用时候有自己的实现
首先要设计个原型
实现Cloneable接口
模板(固定的执行流程)
定义冲饮料的机器:
是Java的规范各个数据库厂商去实现
1、加载驱动类DriverManager
2、建立连接
3、创建语句集(标准语句集、预处理语句集)(语句集合?Mysqloraclesqlserveraccess语句不太一样哦)
4、执行语句集
5、结果集ResultSet游标
ORM(连接的是哪个对象映射哪个结果Listor自定义的类还是??运行时候才知道)