mybatis拦截器(插件)原理

所谓的插件其实就是方法拦截器,mybatis四大组件(Executor,StatementHandler,ParameterHandler,ResultSetHandler)在运行过程中,对四大组件方法拦截并增强,底层用的jdk的动态代理实现,常见的插件有分页插件,mybatis-plus中的MybatisPlusInterceptor,自定义插件,如在插入数据库前设置一些默认的字段(这个在mybatis-plus中不需要拦截器实现)等。

普通方法的拦截

先用mybatis中的Interceptor对普通方法拦截,加强理解

接口:

package mybatis;/*** 接口,jdk动态代理必须有接口*/
public interface TestInterface {String test();String test(String arg1);
}

实现类:

package mybatis;/*** 实现类* 我们这里拟定目标,只增强待参数的test 方法*/
public class TestInterfaceImpl implements TestInterface{@Overridepublic String test() {return "无参";}@Overridepublic String test(String arg1) {return arg1;}
}

方法拦截器

package mybatis;import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;import java.util.Properties;/*** 拦截器,作用:* 指定拦截哪个类的哪个方法(Intercepts注解)* 方法的增强逻辑(intercept方法)* 对目标对象包装,创建动态代理代替目标对象(plugin方法)*/
// 指定拦截哪个接口的哪个方法
@Intercepts({@Signature(type = TestInterface.class, method = "test", args = {String.class})}
)
public class MyInterceptor implements Interceptor {// 增强逻辑写在这里,invocation 里可以获取目标对象,参数信息@Overridepublic Object intercept(Invocation invocation) throws Throwable {Object[] args = invocation.getArgs();// 拦截的是TestInterface接口实现类的方法,所以可以直接强转TestInterface target = (TestInterface) invocation.getTarget();System.out.println("进入方法拦截。。。。。");return target.test(args[0].toString());}// 创建代理对象提供给客户端,底层用的jdk的动态代理@Overridepublic Object plugin(Object target) {return Interceptor.super.plugin(target);}// 目前没用上@Overridepublic void setProperties(Properties properties) {Interceptor.super.setProperties(properties);}
}

单元测试类:

    @Testpublic void testPlug() {// 需要增强的对象TestInterface testInterface = new TestInterfaceImpl();// 创建两个拦截器对象,一个目标可以被多个拦截器包装,一层一层的执行Interceptor myInterceptor = new MyInterceptor();Interceptor myInterceptor2 = new MyInterceptor();//执行一次wrap方法就创建一个代理对象,wrap执行后相当于:Proxy1(target)testInterface = (TestInterface) Plugin.wrap(testInterface, myInterceptor);// 第二次wrap方法执行后相当于:Proxy(Proxy1(target))testInterface = (TestInterface) Plugin.wrap(testInterface, myInterceptor2);// 执行被拦截的方法System.out.println(testInterface.test("hello"));}

结果:

进入方法拦截。。。。。
进入方法拦截。。。。。
helloProcess finished with exit code 0

可以看到两个增强器的方法都执行了

Mybatis 插件原理分析

mybatis 框架只对四大组件设置了插件机制
1、组件是怎么添加到框架中的,拦截器对象保存在哪?
项目启动时解析mybatis 配置文件,解析xml中的plugins 标签或者配置类中通过java代码直接添加。
保存在一个集合中,InterceptorChain 类中的List interceptors 成员变量中

2、何时创建代理对目标进行包装?
Configuration 创建四大组件时,如果有插件,就会创建代理对象,可以是多个,多个代理对象嵌套形成责任链,源码如下:

 public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);// 用所有的拦截器包装parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);return parameterHandler;}public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,ResultHandler resultHandler, BoundSql boundSql) {ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);// 用所有的拦截器包装resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);return resultSetHandler;}public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);// 用所有的拦截器包装statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);return statementHandler;}public Executor newExecutor(Transaction transaction) {return newExecutor(transaction, defaultExecutorType);}public Executor newExecutor(Transaction transaction, ExecutorType executorType) {executorType = executorType == null ? defaultExecutorType : executorType;executorType = executorType == null ? ExecutorType.SIMPLE : executorType;Executor executor;if (ExecutorType.BATCH == executorType) {executor = new BatchExecutor(this, transaction);} else if (ExecutorType.REUSE == executorType) {executor = new ReuseExecutor(this, transaction);} else {executor = new SimpleExecutor(this, transaction);}if (cacheEnabled) {executor = new CachingExecutor(executor);}// 用所有的拦截器包装executor = (Executor) interceptorChain.pluginAll(executor);return executor;}

包装源码:
interceptorChain.pluginAll 最终会调用到Plugin.wrap(target, this) 方法,过程比较简单, wrap() 方法源码

package org.apache.ibatis.plugin;
/*** @author Clinton Begin*/
public class Plugin implements InvocationHandler {private final Object target;private final Interceptor interceptor;private final Map<Class<?>, Set<Method>> signatureMap;// 创建动态代理,将interceptor(拦截器中对目标增强逻辑) 包装成Plugin对象 (动态代理的InvocationHandler)public static Object wrap(Object target, Interceptor interceptor) {Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);Class<?> type = target.getClass();Class<?>[] interfaces = getAllInterfaces(type, signatureMap);if (interfaces.length > 0) {return Proxy.newProxyInstance(type.getClassLoader(),interfaces,new Plugin(target, interceptor, signatureMap));}return target;}
// jdk 动态代理,代理对象执行目标方法前先执行invoke方法@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {try {Set<Method> methods = signatureMap.get(method.getDeclaringClass());if (methods != null && methods.contains(method)) {//  @Signature 注解中标注了的方法才回去拦截return interceptor.intercept(new Invocation(target, method, args));}// 否则直接执行目标方法return method.invoke(target, args);} catch (Exception e) {throw ExceptionUtil.unwrapThrowable(e);}}
}

3、只拦截目标类的目标方法(其他普通方法不拦截)的逻辑在哪实现,如何调用到Interceptor的interceptor方法?
上面Plugin类中的invoke方法执行时,会对方法进行判断是否需要增强,然后执行interceptor.intercept

总结

mybatis 的插件原理其实还是比较简单的,对四大组件指定的方法实现方法增强,多个拦截器就会创建多个代理对象,一层层包装,形成拦截器链,逐个执行。
另:
四大组件一般就Executor(最常见) 和StatementHandler用的多一些,mybatis-plus中
MybatisPlusInterceptor对Executor的query,update,StatementHandler的prepare 方法都进行了拦截,在其中设置了内部拦截器(InnerInterceptor),在不同执行时机都预留了接口,实现拦截器时直接都是实现内部拦截器,这样就不需要创建多个代理对象,性能应该会好一点。

Mybatis 拦截器原理相关推荐

  1. 面试官:你能说说MyBatis拦截器原理吗?

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试资料 作者:Format cnblogs.com/fangjian042 ...

  2. MyBatis拦截器原理探究MyBatis拦截器原理探究

    MyBatis拦截器介绍 MyBatis提供了一种插件(plugin)的功能,虽然叫做插件,但其实这是拦截器功能.那么拦截器拦截MyBatis中的哪些内容呢? 我们进入官网看一看: MyBatis拦截 ...

  3. MyBatis拦截器原理探究

    MyBatis拦截器介绍 MyBatis提供了一种插件(plugin)的功能,虽然叫做插件,但其实这是拦截器功能.那么拦截器拦截MyBatis中的哪些内容呢? 我们进入官网看一看: MyBatis 允 ...

  4. MyBatis拦截器原理探究MyBatis拦截器原理探究 1

    MyBatis拦截器介绍 MyBatis提供了一种插件(plugin)的功能,虽然叫做插件,但其实这是拦截器功能.那么拦截器拦截MyBatis中的哪些内容呢? 我们进入官网看一看: MyBatis拦截 ...

  5. mybatis拦截器,并使用jsqlparser重写sql

    mybatis拦截器 mybatis拦截器基础知识 编写类继承mybatis的Interceptor接口,并实现其相关方法.mybatis的拦截器,是被动态代理类主动调用的. intercept:在拦 ...

  6. Mybatis Interceptor 拦截器原理 源码分析

    Mybatis采用责任链模式,通过动态代理组织多个拦截器(插件),通过这些拦截器可以改变Mybatis的默认行为(诸如SQL重写之类的),由于插件会深入到Mybatis的核心,因此在编写自己的插件前最 ...

  7. Mybatis源码分析之(六)mybatis拦截器(Interceptor)的实现原理

    文章目录 前言 InterceptorChain保存所有的Interceptor 创建四大对象都走Configuration InterceptorChain增强对象方法 Plugin封装动态代理,让 ...

  8. Mybatis 拦截器执行原理分析

    目录 1.拦截器执行流程 2.拦截器实现原理 3.拦截器用法 1.mybatis拦截器实现原理 2.拦截器实现原理 在Mybaits中 拦截器需实现Interceptor接口,加上如下注解 @Inte ...

  9. Mybatis 拦截器介绍

    Mybatis 拦截器介绍 1.1 目录 1.2 前言 1.3 Interceptor接口 1.4 注册拦截器 1.5 Mybatis可拦截的方法 1.6 利用拦截器进行分页 拦截器的一个作用就是我们 ...

最新文章

  1. 盘点丨机器学习2017年重大进展汇总
  2. ActiveMQ学习笔记(二) JMS与Spring
  3. java项目怎么定义异常_在Java项目中如何实现自定义异常
  4. C++图解前缀树(字典树)
  5. libxml -- 解析 XML 文档
  6. c 语言输出指针的值,C 语言指针
  7. [BZOJ3791]作业
  8. Spring MVC初见面
  9. 条件概率的一些结论以及理解
  10. c#仿照qq登录界面编辑框内容操作
  11. 【剑指offer】数组中的逆序对
  12. java 类加载器卸载,【深入明白Java虚拟机 】类加载器的命名空间以及类的卸载...
  13. setinterval 和 ajax,JavaScriptsetInterval和“this”解决方案
  14. RedHat6使用centos6的yum源
  15. 基于Springboot实现高校社团管理系统
  16. c语言long类型转换成string,如何在C ++中将long转换为string?
  17. 计算机电子预览室配置清单,完整的停车场管理系统设计方案-附停车场管理详细大样图及安装图!...
  18. mne plot出错_MNE-Python 环境配置 | win 10
  19. 对于大数据的一些处理方法
  20. 本想搞清楚ESM和CJS模块的互相转换问题,没想到写完我的问题更多了

热门文章

  1. CentOS 7.9 使用Docker安装悟空72crm-11.0-Spring
  2. 垂直行业公众号推广(二) 导入客户
  3. 深度学习——动态调整学习率方案
  4. SUMO ubuntu 源码安装
  5. JavaBean 总结(一个封装类)
  6. 怎么了,不知道做什么了
  7. 游戏原画用的是什么软件?学习难易程度怎么样?我们一起来看看吧
  8. suse zypper mysql_SUSE系统安装软件方法及zypper的相关说明
  9. 鸿蒙手机百万,华为偷梁换柱,“鸿蒙手机”突破百万台,谷歌猝不及防
  10. ubuntu安装swoole