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

代理链的生成

Mybatis支持对Executor、StatementHandler、PameterHandler和ResultSetHandler进行拦截,也就是说会对这4种对象进行代理。

通过查看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, ExecutorType executorType, boolean autoCommit) {
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, autoCommit);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}

接下来让我们通过分析源代码的方式来解读Mybatis的拦截器实现原理

对于拦截器Mybatis为我们提供了一个Interceptor接口,通过实现该接口就可以定义我们自己的拦截器。我们先来看一下这个接口的定义:

 1 package org.apache.ibatis.plugin;
2
3 import java.util.Properties;
4
5 public interface Interceptor {
6
7 Object intercept(Invocation invocation) throws Throwable;
8
9 Object plugin(Object target);
10
11 void setProperties(Properties properties);
12
13 }

我们可以看到在该接口中一共定义有三个方法,intercept、plugin和setProperties。plugin方法是拦截器用于封装目标对象的,通过该方法我们可以返回目标对象本身,也可以返回一个它的代理。当返回的是代理的时候我们可以对其中的方法进行拦截来调用intercept方法,当然也可以调用其他方法,这点将在后文讲解。setProperties方法是用于在Mybatis配置文件中指定一些属性的。

定义自己的Interceptor最重要的是要实现plugin方法和intercept方法,在plugin方法中我们可以决定是否要进行拦截进而决定要返回一个什么样的目标对象。而intercept方法就是要进行拦截的时候要执行的方法。

对于plugin方法而言,其实Mybatis已经为我们提供了一个实现。Mybatis中有一个叫做Plugin的类,里面有一个静态方法wrap(Object target,Interceptor interceptor),通过该方法可以决定要返回的对象是目标对象还是对应的代理。这里我们先来看一下Plugin的源码:

  1 package org.apache.ibatis.plugin;
2
3 import java.lang.reflect.InvocationHandler;
4 import java.lang.reflect.Method;
5 import java.lang.reflect.Proxy;
6 import java.util.HashMap;
7 import java.util.HashSet;
8 import java.util.Map;
9 import java.util.Set;
10
11 import org.apache.ibatis.reflection.ExceptionUtil;
12
13 //这个类是Mybatis拦截器的核心,大家可以看到该类继承了InvocationHandler
14 //又是JDK动态代理机制
15 public class Plugin implements InvocationHandler {
16
17 //目标对象
18 private Object target;
19 //拦截器
20 private Interceptor interceptor;
21 //记录需要被拦截的类与方法
22 private Map<Class<?>, Set<Method>> signatureMap;
23
24 private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
25 this.target = target;
26 this.interceptor = interceptor;
27 this.signatureMap = signatureMap;
28 }
29
30 //一个静态方法,对一个目标对象进行包装,生成代理类。
31 public static Object wrap(Object target, Interceptor interceptor) {
32 //首先根据interceptor上面定义的注解 获取需要拦截的信息
33 Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
34 //目标对象的Class
35 Class<?> type = target.getClass();
36 //返回需要拦截的接口信息
37 Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
38 //如果长度为>0 则返回代理类 否则不做处理
39 if (interfaces.length > 0) {
40 return Proxy.newProxyInstance(
41 type.getClassLoader(),
42 interfaces,
43 new Plugin(target, interceptor, signatureMap));
44 }
45 return target;
46 }
47
48 //代理对象每次调用的方法
49 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
50 try {
51 //通过method参数定义的类 去signatureMap当中查询需要拦截的方法集合
52 Set<Method> methods = signatureMap.get(method.getDeclaringClass());
53 //判断是否需要拦截
54 if (methods != null && methods.contains(method)) {
55 return interceptor.intercept(new Invocation(target, method, args));
56 }
57 //不拦截 直接通过目标对象调用方法
58 return method.invoke(target, args);
59 } catch (Exception e) {
60 throw ExceptionUtil.unwrapThrowable(e);
61 }
62 }
63
64 //根据拦截器接口(Interceptor)实现类上面的注解获取相关信息
65 private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
66 //获取注解信息
67 Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
68 //为空则抛出异常
69 if (interceptsAnnotation == null) { // issue #251
70 throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
71 }
72 //获得Signature注解信息
73 Signature[] sigs = interceptsAnnotation.value();
74 Map<Class<?>, Set<Method>> signatureMap = new HashMap<Class<?>, Set<Method>>();
75 //循环注解信息
76 for (Signature sig : sigs) {
77 //根据Signature注解定义的type信息去signatureMap当中查询需要拦截方法的集合
78 Set<Method> methods = signatureMap.get(sig.type());
79 //第一次肯定为null 就创建一个并放入signatureMap
80 if (methods == null) {
81 methods = new HashSet<Method>();
82 signatureMap.put(sig.type(), methods);
83 }
84 try {
85 //找到sig.type当中定义的方法 并加入到集合
86 Method method = sig.type().getMethod(sig.method(), sig.args());
87 methods.add(method);
88 } catch (NoSuchMethodException e) {
89 throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
90 }
91 }
92 return signatureMap;
93 }
94
95 //根据对象类型与signatureMap获取接口信息
96 private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
97 Set<Class<?>> interfaces = new HashSet<Class<?>>();
98 //循环type类型的接口信息 如果该类型存在与signatureMap当中则加入到set当中去
99 while (type != null) {
100 for (Class<?> c : type.getInterfaces()) {
101 if (signatureMap.containsKey(c)) {
102 interfaces.add(c);
103 }
104 }
105 type = type.getSuperclass();
106 }
107 //转换为数组返回
108 return interfaces.toArray(new Class<?>[interfaces.size()]);
109 }
110
111 }

Plugin源代码分析

下面是俩个注解类的定义源码

 1 package org.apache.ibatis.plugin;
2
3 import java.lang.annotation.ElementType;
4 import java.lang.annotation.Retention;
5 import java.lang.annotation.RetentionPolicy;
6 import java.lang.annotation.Target;
7
8 @Retention(RetentionPolicy.RUNTIME)
9 @Target(ElementType.TYPE)
10 public @interface Intercepts {
11 Signature[] value();
12 }
 1 package org.apache.ibatis.plugin;
2
3 import java.lang.annotation.ElementType;
4 import java.lang.annotation.Retention;
5 import java.lang.annotation.RetentionPolicy;
6 import java.lang.annotation.Target;
7
8 @Retention(RetentionPolicy.RUNTIME)
9 @Target(ElementType.TYPE)
10 public @interface Signature {
11 Class<?> type();
12
13 String method();
14
15 Class<?>[] args();
16 }

http://www.cnblogs.com/daxin/p/3541922.html

Mybatis Interceptor 拦截器原理 源码分析的更多相关文章

  1. SpringBoot拦截器及源码分析

    1.拦截器是什么 java里的拦截器(Interceptor)是动态拦截Action调用的对象,它提供了一种机制可以使开发者在一个Action执行的前后执行一段代码,也可以在一个Action执行前阻止 ...

  2. 7.Struts2拦截器及源码分析

    1.Struts2架构图 2.Struts2 执行过程分析 1.首先,因为使用 struts2 框架,请求被Struts2Filter 拦截 2.Struts2Filter  调用 DisPatche ...

  3. MyBatis框架的使用及源码分析(十一) StatementHandler

    我们回忆一下<MyBatis框架的使用及源码分析(十) CacheExecutor,SimpleExecutor,BatchExecutor ,ReuseExecutor> , 这4个Ex ...

  4. MyBatis框架的使用及源码分析(九) Executor

    从<MyBatis框架的使用及源码分析(八) MapperMethod>文中我们知道执行Mapper的每一个接口方法,最后调用的是MapperMethod.execute方法.而当执行Ma ...

  5. mybatis Interceptor拦截器代码详解

    mybatis官方定义:MyBatis 是一款优秀的持久层框架,它支持定制化 SQL.存储过程以及高级映射.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis ...

  6. [五]类加载机制双亲委派机制 底层代码实现原理 源码分析 java类加载双亲委派机制是如何实现的

      Launcher启动类 本文是双亲委派机制的源码分析部分,类加载机制中的双亲委派模型对于jvm的稳定运行是非常重要的 不过源码其实比较简单,接下来简单介绍一下   我们先从启动类说起 有一个Lau ...

  7. Mybatis之拦截器原理(jdk动态代理优化版本)

    在介绍Mybatis拦截器代码之前,我们先研究下jdk自带的动态代理及优化 其实动态代理也是一种设计模式...优于静态代理,同时动态代理我知道的有两种,一种是面向接口的jdk的代理,第二种是基于第三方 ...

  8. concurrent(六)同步辅助器CyclicBarrier & 源码分析

    参考文档:Java多线程系列--“JUC锁”10之 CyclicBarrier原理和示例:https://www.cnblogs.com/skywang12345/p/3533995.html简介Cy ...

  9. guava eventbus 原理+源码分析

    前言: guava提供的eventbus可以很方便的处理一对多的事件问题, 最近正好使用到了,做个小结,使用的demo网上已经很多了,不再赘述,本文主要是源码分析+使用注意点+新老版本eventbus ...

随机推荐

  1. 水王ID

    题目: 三人行设计了一个灌水论坛.信息学院的学生都喜欢在上面交流灌水,传说在论坛上有一个“水王”,他不但喜欢发帖,还会回复其他ID发的每个帖子.坊间风闻该“水王”发帖数目超过了帖子数目的一半. 如果你 ...

  2. throttle/debounce: 为你的cpu减减压(前端性能优化)

    何为throttle, 何为debounce? 谷歌翻译给出的意思:throttle 掐死???   debounce 去抖 好吧,按理解我们习惯翻译成 ——节流. 那么在什么场景下需要用到? 场景一 ...

  3. Netsharp快速入门(之2) 基础档案(之A 创建插件和资源)

    作者:秋时 杨昶   时间:2014-02-15  转载须说明出处 第三章     基础档案开发 本文不再对此需求进行分析设计,其实分析设计的结果在下文会体现在平台的使用过程中,这个销售系统分成两个模 ...

  4. 重新认识Box Model、IFC、BFC和Collapsing margins

    尊重原创,转载自: http://www.cnblogs.com/fsjohnhuang/p/5259121.html 肥子John^_^ 前言   盒子模型作为CSS基础中的基础,曾一度以为掌握了I ...

  5. sentinel.conf配置

    1.常用的配置 port 26379 # sentinel announce-ip <ip> # sentinel announce-port <port> dir /tmp ...

  6. git学习——<一>git安装

    一.windows.linux平台安装 windows平台安装简单方便,到git官网上下载exe安装包即可,会把git bash shell给你安装好,你到命令窗口便可直接使用. linux平台安装, ...

  7. A*(A星)算法python实现

    在春节放假前两天我偶然看到了A\*算法(A\*算法是一个启发式的地图寻路算法),感觉挺有意思.正好放假前也没有什么事情,就花了一个下午写出算法的骨架,节后又花了半天时间完善屏幕输出的细节并且调试完成. ...

  8. shader 里面的分支

    shader 里面的真分支会降低效率 一种方法:构造一个分段函数出来 比如saturate(depth*1.5f)

  9. HDU-2604_Queuing

    题目:Problem Description Queues and Priority Queues are data structures which are known to most comput ...

  10. JavaScript 语言基础知识点总结(思维导图)

    JavaScript 数组 JavaScript 函数基础 Javascript 运算符 JavaScript 流程控制 JavaScript 正则表达式 JavaScript 字符串函数 JavaS ...