转载自  mybatis源码阅读(八) ---Interceptor了解一下

1 Intercetor

MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis允许使用插件来拦截的方法调用包括:

Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed) 拦截执行器的方法
ParameterHandler (getParameterObject, setParameters) 拦截参数的处理
ResultSetHandler (handleResultSets, handleOutputParameters) 拦截结果集的处理
StatementHandler (prepare, parameterize, batch, update, query) 拦截Sql语法构建的处理

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里保存了所有的拦截器,它在mybatis初始化的时候创建。上面interceptorChain.pluginAll(executor)的含义是调用拦截器链里的每个拦截器依次对executor进行plugin(插入拦截)代码如下

public class InterceptorChain {private final List<Interceptor> interceptors = new ArrayList<Interceptor>();public Object pluginAll(Object target) {for (Interceptor interceptor : interceptors) {target = interceptor.plugin(target);}return target;}public void addInterceptor(Interceptor interceptor) {interceptors.add(interceptor);}public List<Interceptor> getInterceptors() {return Collections.unmodifiableList(interceptors);}}

Interceptor 结构

public interface Interceptor {Object intercept(Invocation invocation) throws Throwable;Object plugin(Object target);void setProperties(Properties properties);}

2.自定义拦截器

官方源码例子

@Intercepts({@Signature(type = Executor.class, method = "query",args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})
public class ExamplePlugin implements Interceptor {private Properties properties;@Overridepublic Object intercept(Invocation invocation) throws Throwable {return invocation.proceed();}@Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}@Overridepublic void setProperties(Properties properties) {this.properties = properties;}public Properties getProperties() {return properties;}}

mybatis-config.xml配置

<plugins><plugin interceptor="org.lpf.interceptor.ExamplePlugin"></plugin>
</plugins>

每一个拦截器都必须实现上面的三个方法,其中:

Object intercept(Invocation invocation)是实现拦截逻辑的地方,内部要通过invocation.proceed()显式地推进责任链前进,也就是调用下一个拦截器拦截目标方法。

Object plugin(Object target)就是用当前这个拦截器生成对目标target的代理,实际是通过Plugin.wrap(target,this)来完成的,把目标target和拦截器this传给了包装函数。

setProperties(Properties properties)用于设置额外的参数,参数配置在拦截器的Properties节点里。

注解里描述的是指定拦截方法的签名 [type,method,args] (即对哪种对象的哪种方法进行拦截),它在拦截前用于决断。

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

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

/*** @author Clinton Begin* 这个类是Mybatis拦截器的核心,大家可以看到该类继承了InvocationHandler* 又是JDK动态代理机制*/
public class Plugin implements InvocationHandler {//目标对象private final Object target;// 拦截器private final Interceptor interceptor;//记录需要被拦截的类与方法 提高性能private final Map<Class<?>, Set<Method>> signatureMap;private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {this.target = target;this.interceptor = interceptor;this.signatureMap = signatureMap;}//一个静态方法,对一个目标对象进行包装,生成代理类。public static Object wrap(Object target, Interceptor interceptor) {//首先根据interceptor上面定义的注解 获取需要拦截的信息Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);//目标对象的ClassClass<?> type = target.getClass();//返回需要拦截的接口信息Class<?>[] interfaces = getAllInterfaces(type, signatureMap);//如果长度为>0 则返回代理类 否则不做处理if (interfaces.length > 0) {return Proxy.newProxyInstance(type.getClassLoader(),interfaces,new Plugin(target, interceptor, signatureMap));}return target;}//代理对象每次调用的方法@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {try {//通过method参数定义的类 去signatureMap当中查询需要拦截的方法集合Set<Method> methods = signatureMap.get(method.getDeclaringClass());//判断是否需要拦截if (methods != null && methods.contains(method)) {//执行拦截器的拦截方法return interceptor.intercept(new Invocation(target, method, args));}//不拦截 直接通过目标对象调用方法return method.invoke(target, args);} catch (Exception e) {throw ExceptionUtil.unwrapThrowable(e);}}//根据拦截器接口(Interceptor)实现类上面的注解获取相关信息private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {//获取注解信息@InterceptsIntercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);// issue #251if (interceptsAnnotation == null) {//为空则抛出异常throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());      }//获得Signature注解信息  是一个数组Signature[] sigs = interceptsAnnotation.value();Map<Class<?>, Set<Method>> signatureMap = new HashMap<Class<?>, Set<Method>>();//循环注解信息for (Signature sig : sigs) {//根据Signature注解定义的type信息去signatureMap当中查询需要拦截方法的集合Set<Method> methods = signatureMap.get(sig.type());if (methods == null) { //第一次肯定为null 就创建一个并放入signatureMapmethods = new HashSet<Method>();signatureMap.put(sig.type(), methods);}try {//找到sig.type当中定义的方法 并加入到集合Method method = sig.type().getMethod(sig.method(), sig.args());methods.add(method);} catch (NoSuchMethodException e) {throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);}}return signatureMap;}//根据对象类型与signatureMap获取接口信息private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {Set<Class<?>> interfaces = new HashSet<Class<?>>();//循环type类型的接口信息 如果该类型存在与signatureMap当中则加入到set当中去while (type != null) {for (Class<?> c : type.getInterfaces()) {if (signatureMap.containsKey(c)) {interfaces.add(c);}}type = type.getSuperclass();}//转换为数组返回return interfaces.toArray(new Class<?>[interfaces.size()]);}}

3.代理链上的拦截

我们再次结合(Executor)interceptorChain.pluginAll(executor)这个语句来看,这个语句内部对executor执行了多次plugin,第一次plugin后通过Plugin.wrap方法生成了第一个代理类,姑且就叫executorProxy1,这个代理类的target属性是该executor对象。第二次plugin后通过Plugin.wrap方法生成了第二个代理类,姑且叫executorProxy2,这个代理类的target属性是executorProxy1...这样通过每个代理类的target属性就构成了一个代理链(从最后一个executorProxyN往前查找,通过target属性可以找到最原始的executor类)

当Configuration调用newExecutor方法的时候,由于Executor接口的update(MappedStatement ms, Object parameter)方法被拦截器被截获。因此最终返回的是一个代理类Plugin,而不是Executor。这样调用方法的时候,如果是个代理类,那么会执行:

//代理对象每次调用的方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {try {//通过method参数定义的类 去signatureMap当中查询需要拦截的方法集合Set<Method> methods = signatureMap.get(method.getDeclaringClass());//判断是否需要拦截if (methods != null && methods.contains(method)) {//执行拦截器的拦截方法return interceptor.intercept(new Invocation(target, method, args));}//不拦截 直接通过目标对象调用方法return method.invoke(target, args);} catch (Exception e) {throw ExceptionUtil.unwrapThrowable(e);}
}

那么会执行Interceptor接口的interceptor方法 如下

public Object intercept(Invocation invocation) throws Throwable {return invocation.proceed();
}

传递给拦截器的是一个Invocation对象,如下

public class Invocation {private final Object target;private final Method method;private final Object[] args;public Invocation(Object target, Method method, Object[] args) {this.target = target;this.method = method;this.args = args;}public Object getTarget() {return target;}public Method getMethod() {return method;}public Object[] getArgs() {return args;}public Object proceed() throws InvocationTargetException, IllegalAccessException {return method.invoke(target, args);}

可以看到,Invocation类保存了代理对象的目标类,执行的目标类方法以及传递给它的参数。

在每个拦截器的intercept方法内,最后一个语句一定是return invocation.proceed()(不这么做的话拦截器链就断了,你的mybatis基本上就不能正常工作了)。invocation.proceed()只是简单的调用了下target的对应方法,如果target还是个代理,就又回到了上面的Plugin.invoke方法了。这样就形成了拦截器的调用链推进。

4.总结

总体来说MyBatis拦截器还是很简单的,拦截器本身不需要太多的知识点,但是学习拦截器需要对MyBatis中的各个接口很熟悉,因为拦截器涉及到了各个接口的知识点。

我们假设在MyBatis配置了一个插件,在运行时会发生什么?

  1. 所有可能被拦截的处理类都会生成一个代理
  2. 处理类代理在执行对应方法时,判断要不要执行插件中的拦截方法
  3. 执行插接中的拦截方法后,推进目标的执行

如果有N个插件,就有N个代理,每个代理都要执行上面的逻辑。这里面的层层代理要多次生成动态代理,是比较影响性能的。虽然能指定插件拦截的位置,但这个是在执行方法时动态判断,初始化的时候就是简单的把插件包装到了所有可以拦截的地方。

因此,在编写插件时需注意以下几个原则:

  1. 不编写不必要的插件;
  2. 实现plugin方法时判断一下目标类型,是本插件要拦截的对象才执行Plugin.wrap方法,否者直接返回目标本省,这样可以减少目标被代理的次数。

mybatis源码阅读(八) ---Interceptor了解一下相关推荐

  1. mybatis源码阅读(二):mybatis初始化上

    转载自  mybatis源码阅读(二):mybatis初始化上 1.初始化入口 //Mybatis 通过SqlSessionFactory获取SqlSession, 然后才能通过SqlSession与 ...

  2. mybatis源码阅读(七) ---ResultSetHandler了解一下

    转载自  mybatis源码阅读(七) ---ResultSetHandler了解一下 1.MetaObject MetaObject用于反射创建对象.反射从对象中获取属性值.反射给对象设置属性值,参 ...

  3. mybatis源码阅读(六) ---StatementHandler了解一下

    转载自  mybatis源码阅读(六) ---StatementHandler了解一下 StatementHandler类结构图与接口设计 BaseStatementHandler:一个抽象类,只是实 ...

  4. mybatis源码阅读(五) ---执行器Executor

    转载自  mybatis源码阅读(五) ---执行器Executor 1. Executor接口设计与类结构图 public interface Executor {ResultHandler NO_ ...

  5. mybatis源码阅读(四):mapper(dao)实例化

    转载自   mybatis源码阅读(四):mapper(dao)实例化 在开始分析之前,先来了解一下这个模块中的核心组件之间的关系,如图: 1.MapperRegistry&MapperPro ...

  6. mybatis源码阅读(三):mybatis初始化(下)mapper解析

    转载自 mybatis源码阅读(三):mybatis初始化(下)mapper解析 MyBatis 的真正强大在于它的映射语句,也是它的魔力所在.由于它的异常强大,映射器的 XML 文件就显得相对简单. ...

  7. mybatis源码阅读(一):SqlSession和SqlSessionFactory

    转载自  mybatis源码阅读(一):SqlSession和SqlSessionFactory 一.接口定义 听名字就知道这里使用了工厂方法模式,SqlSessionFactory负责创建SqlSe ...

  8. Mybatis源码阅读(一):Mybatis初始化1.1 解析properties、settings

    *************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 如 ...

  9. Mybatis源码阅读之二——模板方法模式与Executor

    [系列目录] Mybatis源码阅读之一--工厂模式与SqlSessionFactory 文章目录 一. 模板方法模式 二. 同步回调与匿名函数 三. Executor BaseExecutor与其子 ...

最新文章

  1. Oracle day05 索引_数据去重
  2. [Machine Learning]--知识点疑问汇总[持续更新中]
  3. sqlserver 查看进程以及查看死锁代码
  4. python server.py_python manage.py runserver报错
  5. oct玻璃体后脱离图像
  6. Java 设计模式(3)单例模式
  7. 线性代数学习笔记(二)
  8. python库之pandas
  9. Get!程序员成为高级管理者的三次跃升
  10. 有关css3的一些问题
  11. 撸了个搜索引擎系统,爽!
  12. netty 学习 (1)
  13. 算法题目打卡:Ques20201025
  14. 源支付源码三网免挂带云端协议源码搭建
  15. 理想,努力了才叫梦想,放弃了那只是妄想
  16. 在Windows 7和Vista中禁用程序兼容性助手
  17. LINUX FTP用户的创建
  18. c语言|程序设计|指针~字母出现次数(1)
  19. 英语中的现在分词和过去分词
  20. 拼车 vs 乘坐公交

热门文章

  1. 岛屿类问题的广度优先深度优先双解法(Leetcode题解-Python语言)
  2. 每天一小时python官方文档学习(五)————数据结构之元组、集合与字典
  3. 堆的定义与操作(C语言)
  4. [Java基础]Map集合基础
  5. C++ 泛型编程 实现红黑树RBTree
  6. 自动备份html文件,windows下定期自动备份本地文件(文件夹)
  7. java解压中文乱码_java使用解压zip文件,文件名乱码解决方案
  8. c语言枚举类型例题_[开源资讯]Zig 0.6.0 发布,想要挑战 C 语言
  9. Codeforces Round #606 (Div. 2, based on Technocup 2020 Elimination Round 4) dfs + 思维
  10. Educational Codeforces Round 103 (Rated for Div. 2) D. Journey dp