Mybatis Interceptor 拦截器原理 源码分析
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 拦截器原理 源码分析的更多相关文章
- SpringBoot拦截器及源码分析
1.拦截器是什么 java里的拦截器(Interceptor)是动态拦截Action调用的对象,它提供了一种机制可以使开发者在一个Action执行的前后执行一段代码,也可以在一个Action执行前阻止 ...
- 7.Struts2拦截器及源码分析
1.Struts2架构图 2.Struts2 执行过程分析 1.首先,因为使用 struts2 框架,请求被Struts2Filter 拦截 2.Struts2Filter 调用 DisPatche ...
- MyBatis框架的使用及源码分析(十一) StatementHandler
我们回忆一下<MyBatis框架的使用及源码分析(十) CacheExecutor,SimpleExecutor,BatchExecutor ,ReuseExecutor> , 这4个Ex ...
- MyBatis框架的使用及源码分析(九) Executor
从<MyBatis框架的使用及源码分析(八) MapperMethod>文中我们知道执行Mapper的每一个接口方法,最后调用的是MapperMethod.execute方法.而当执行Ma ...
- mybatis Interceptor拦截器代码详解
mybatis官方定义:MyBatis 是一款优秀的持久层框架,它支持定制化 SQL.存储过程以及高级映射.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis ...
- [五]类加载机制双亲委派机制 底层代码实现原理 源码分析 java类加载双亲委派机制是如何实现的
Launcher启动类 本文是双亲委派机制的源码分析部分,类加载机制中的双亲委派模型对于jvm的稳定运行是非常重要的 不过源码其实比较简单,接下来简单介绍一下 我们先从启动类说起 有一个Lau ...
- Mybatis之拦截器原理(jdk动态代理优化版本)
在介绍Mybatis拦截器代码之前,我们先研究下jdk自带的动态代理及优化 其实动态代理也是一种设计模式...优于静态代理,同时动态代理我知道的有两种,一种是面向接口的jdk的代理,第二种是基于第三方 ...
- concurrent(六)同步辅助器CyclicBarrier & 源码分析
参考文档:Java多线程系列--“JUC锁”10之 CyclicBarrier原理和示例:https://www.cnblogs.com/skywang12345/p/3533995.html简介Cy ...
- guava eventbus 原理+源码分析
前言: guava提供的eventbus可以很方便的处理一对多的事件问题, 最近正好使用到了,做个小结,使用的demo网上已经很多了,不再赘述,本文主要是源码分析+使用注意点+新老版本eventbus ...
随机推荐
- 深入理解CSS3 animation的steps
在应用 CSS3 渐变/动画时,有个控制时间的属性 <timing-function> .它的取值中除了常用到的三次贝塞尔曲线以外,还有个让人比较困惑的 steps() 函数. steps ...
- Java团队项目总结
Java团队项目总结 1.项目实现情况 项目概述: 我们团队项目准备实现一个有关于大富翁有的游戏程序. 大富翁游戏,以经营权为主要的游戏方式,通过购买经营权与架构经营的星级服务来获得最大的利益,当其他 ...
- Codeforces Round #354 (Div. 2) D. Theseus and labyrinth
题目链接: http://codeforces.com/contest/676/problem/D 题意: 如果两个相邻的格子都有对应朝向的门,则可以从一个格子到另一个格子,给你初始坐标xt,yt,终 ...
- 将通过find命令找到的文件拷贝到一个新的目录中
将通过find命令找到的文件拷贝到一个新的目录中 有这样的一个需求,需要将一部分符合条件的文件从一个目录拷贝到另一个目录中,我通过find命令从源目录查找到符合条件的文件然后使用cp命令拷贝到目标目录 ...
- opencv中的Bayes分类器应用实例
转载:http://blog.csdn.net/yang_xian521/article/details/6967515 PS:很多时候,我们并不需要特别精通某个理论,而且有的时候即便你非常精通,但是 ...
- ios读取通讯录信息
ios读取通讯录信息 (2012-05-22 14:07:11) 标签: ios读取通讯录 it iphone如许app读取通讯录信息,读取通讯录信息时需要加载AddressBookUI 和Add ...
- quickeys 过期解决办法
自己在系统中找了大半天,终于发现了一个叫.com.quickeys.quickeysX3.plist的文件,存在于硬盘的 ~/Library/Preferences/ 目录下,直觉感觉文件的大体内容应 ...
- [初级教程]用SecureCRT+Xming轻松远程实现Linux的X DISPLAY
[初级教程]用SecureCRT+Xming轻松远程实现Linux的X DISPLAY 发布者:sqqdugdu 时间:10-06 阅读数:2117 测试环境:RHEL 6.1,SecureCRT 5 ...
- HDU4966 GGS-DDU(最小树形图)
之前几天想着补些算法的知识,学了一下最小树形图的朱刘算法,不是特别理解,备了份模板以备不时之需,想不到多校冷不丁的出了个最小树形图,没看出来只能表示对算法不太理解吧,用模板写了一下,然后就过了.- - ...
- 调用MYSQL存储过程实例
PHP调用MYSQL存储过程实例 http://blog.csdn.net/ewing333/article/details/5906887 http://www.cnblogs.com/kkchen ...