来源:https://www.cnblogs.com/chenp...背景关于Mybatis插件,大多数人都知道并使用过,但很多时候,我们只是停留在表面,对Mybatis插件有所了解-in可以在DAO层进行拦截,比如打印执行的SQL语句日志,做一些权限控制,分页等功能;但往往对其内部实现机制、涉及的软件设计模式、编程思想没有深入的了解。本案例将帮助读者对Mybatis插件涉及的使用场景、实现机制、编程思路进行总结,希望对以后的编程开发工作有所帮助。注:本案例以mybatis3.4.7-SNAPSHOT版本为例。PS:文章是很久以前写的了。当时花了点心思,在电脑上用word保存了下来。今天正好看到了,不过里面的源码全是图片。哈哈哈,就凑合吧。Mybatis插件典型应用场景分页功能mybatis分页默认是基于内存分页(查找所有,然后截取),数据量大时效率低,但是使用mybatis插件可以改变这种行为,只需要拦截StatementHandler类的prepare方法,将要执行的SQL语句改为分页语句即可;通用字段统一赋值通用业务系统会有四个字段:创建者、创建时间、修改者、修改时间。对于这四个字段的赋值,其实可以在DAO层统一拦截处理,可以使用mybatis插件拦截Executor类的update方法,统一给相关参数赋值;性能监控对于SQL语句执行的性能监控,可以拦截Executor类的更新、查询等方法,使用日志记录每个方法的执行时间;其实mybatis的扩展性还是很强的。基于插件机制,基本可以控制SQL执行的各个阶段,如执行阶段、参数处理阶段、语法构建阶段、结果集处理阶段。具体可以根据项目业务实现相应的业务逻辑。Mybatis插件介绍什么是Mybatis插件与其说是Mybatis插件不如说是Mybatis拦截器更符合它的功能定位。其实就是一个拦截器,采用代理模式,在方法级别进行拦截。支持拦截Executor的方法(更新、查询、提交、回滚等);参数处理器ParameterHandler(getParameterObject、setParameters方法);结果集处理器ResultSetHandler(handleResultSets、handleOutputParameters等);SQL语法构建器StatementHandler(准备、参数化、批处理、更新、查询等);拦截阶段那么这些类上的方法是在什么阶段被拦截的呢?为了理解这个问题,我们先来看一段简单的代码(取自mybatis源码中的单元测试SqlSessionTest类)来理解典型的mybatis执行流程,如下代码所示:上面的代码主要完成了以下内容功能:读取mybatis的xml配置文件信息,通过SqlSessionFactoryBuilder创建SqlSessionFactory对象,通过SqlSessionFactory获取SqlSession对象,执行SqlSession对象的selectList方法,查询结果关闭SqlSession。在文中提到的四个类上,拦截了这些类上的方法。Mybatis插件实现机制插件配置信息的加载我们先来看看mybatis是如何加载插件配置的。对应的xml配置信息如下:对应的解析代码如下,主要工作如下:根据解析出的类信息创建Interceptor对象;调用setProperties方法设置属性变量;将它们添加到Configuration的interceptorChain拦截器链中;以上逻辑对应的时序图如下:代理对象的生成Mybatis插件的实现机制主要是基于动态代理实现,其中最关键的是代理对象的生成,所以需要了解这些代理对象是如何生成的。Executor代理对象ParameterHandler代理对象ResultSetHandler代理对象StatementHandler代理对象观察源码,发现这些可拦截类对应的对象生成是通过InterceptorChain的pluginAll方法创建的。进一步观察pluginAll方法如下:遍历所有拦截器,调用拦截器的plugin方法生成一个代理对象。注意生成的代理对象是重新赋值给目标的,所以如果有多个拦截器,生成的代理对象会被另外一个代理对象代理,从而形成一个代理链。执行时,依次执行所有拦截器的拦截逻辑代码;下面我们来看一个典型的写拦截器时的plugin方法实现,如下:进一步看wrap方法,如下:一个典型的动态代理实现调用Proxy.newProxyInstance方法生成一个代理对象。上述逻辑对应的时序图如下。这里我们假设声明了两个拦截器。那么在创建目标代理对象时,最终返回的代理对象proxy2实际上代理了proxy1,proxy1代理了目标:拦截逻辑Execution由于实际执行Executor、ParameterHandler、ResultSetHandler、StatementHandler类中方法的对象是一个代理对象(建议将代理对象转成class文件,反编译查看其结构,有助于理解),所以在执行方法时,先调用Plugin类的invoke方法(实现了InvocationHandler接口)如下:首先根据执行方法的类获取拦截器中声明的需要拦截的方法集合;判断当前方法是否需要执行拦截逻辑,如果需要则执行拦截逻辑方法(即Interceptor接口的拦截方法实现),如果不需要则直接执行原方法。可以关注Interceptor接口的intercept方法的实现。一般需要用户自定义实现逻辑。有一个重要的参数,就是Invocation类。通过改变参数,我们可以获得执行对象,执行方法,以及执行方法上的参数,从而进行各种业务逻辑的实现,一般这个方法最后一行代码是invocation.proceed()(method.invoke方法在内部执行),否则不会执行下一个拦截器的拦截方法。上述逻辑对应的时序图如下。这里以执行executor对象的查询方法为例,假设有两个拦截器:插入。如下图:主要需要实现拦截的三个方法:这里实现自己的拦截逻辑,可以从Invocation参数中获取执行方法的对象、方法、方法参数,从而实现各种业务逻辑,如图在下面的代码中,从调用中得到的statementHandler对象就是被代理的对象。基于这个对象,我们获取原来执行的SQL语句和prepare方法上的分页参数,将SQL语句改成新的分页语句,最后调用invocation.proceed()返回结果。plugin:生成代理对象;setProperties:设置一些属性变量;总结简单来说,mybatis插件就是拦截ParameterHandler、ResultSetHandler、StatementHandler、Executor这四个接口上的方法,利用JDK动态代理机制为实现这些接口的类创建代理对象。执行方法时,先执行代理对象的方法,执行自己写的拦截逻辑。所以,要真正使用mybatis插件,必须熟悉这四个接口的方法,以及这些方法上的参数。另外,如果配置了多个拦截器,会出现层层代理的情况,即一个代理对象代理另一个代理对象,形成一个代理链。执行的时候也是一层层执行;mybatis插件涉及到的设计模式和软件思路如下:设计模式:代理模式、责任链模式;软件思想:AOP编程思想,降低模块之间的耦合度,让业务模块更加独立;一些注意事项:不要定义过多的插件,代理嵌套过多,执行方法时很耗性能;在拦截器实现类的intercept方法中,不要忘记在最后执行invocation.proceed()方法,否则在多个拦截器的情况下执行链会被打断;近期热点文章推荐:1.1000+Java面试题及答案(2022最新版)2.厉害了!Java协程来了。..3.SpringBoot2.x教程,太全面了!4.不要用爆破爆满画面,试试装饰者模式,这才是优雅的方式!!5.《Java开发手册(嵩山版)》最新发布,赶快下载吧!感觉不错,别忘了点赞+转发!
