在上篇文章中,《mybatis源码配置文件解析之四:解析plugins标签 》分析了mybatis中的plugin标签的解析过程,plugin指的是插件,或者说拦截器更为形象,因为它的作用就是拦截特定的方法,根据拦截到的方法进行特定的处理。

一、概述

在mybatis中插件我认为叫拦截器更贴切,下面的统一称为拦截器,在上篇文章中说到了拦截器的使用方式及作用,现在来分析下其实现原理,是如何进行方法拦截的。在上篇最后提到在mybatis核心配置文件中配置的拦截器经过解析后,最终都保存在了InterceptorChai类中的interceptors属性中,如下图,

最终所有的拦截器均保存在了ArrayList中。回过头来看InterceptorChain类,在该类中有pluginAll方法,从该方法的定义分析知道,此方法在遍历所有的拦截器,并调用其plugin方法,既然是遍历拦截器,那就说明在使用拦截器,可以大胆判断pluginAll方法和拦截器的使用有关。搜索mybatis的源码,plugin方法被调用的地方如下,

从上面可以看出在整个源码中仅有四处调用,都是在Configuration类中,这四处调用分别和ParameterHandler、ReslutSetHandler、StatementHandler、Executor有关,从上篇文件可以知道,拦截器拦截的就是这四个接口中的方法,现在可以肯定这里便是拦截器起作用的地方,从这四处中选择statementHandler=(StatementHandler)interceptorChain.pluginAll(statementHandler);来分析其实现原理。

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;
}

二、详述

根据上面的分析,在newStatementHandler方法中调用了pluginAll方法,我们便从newStatementHandler方法开始分析,改方法中首先生成了一个RoutingStatementHandler的实例,此行代码就是单纯的new,暂时不必关注,紧接着下面一句interceptorChain.pluginAll,调用了pluginAll方法,在开篇就提到该方法在InterceptorChain类中。

1、pluginAll方法

下面是pluginAll方法的定义,

public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}

此方法在遍历interceptors即遍历所有的拦截器,调用每个拦截器的plugin方法,plugin方法会返回一个对象,我们看默认的拦截器接口的plugin方法,

public interface Interceptor {

  Object intercept(Invocation invocation) throws Throwable;

  Object plugin(Object target);

  void setProperties(Properties properties);

}

也就是所有实现该接口的方法需要实现上面的三个方法,如果对plugin方法,不进行重写,那么默认会返回null。回到下面这行代码看下能否返回null,

target = interceptor.plugin(target);

如果plugin方法返回null,那么最后pluginAll方法会返回null,该方法返回null,那么下面这行代码便为null,

 statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);

最后newStatementHandler便会为null,我们知道newStatementHandler方法不能为null,为null了mybatis就无法往下处理。所以对于实现了Interceptor接口的类必须重新plugin方法,怎么重新那,该方法如何返回一个对象那,返回一个什么样的对象那,我们知道在newStatementHandler方法中已经new了一个StatementHandler对象,然后调用了pluginAll方法,为什么不直接返回statementHandler对象,而是把statementHandler作为参数调用pluginAll之后返回statementHandler那,因为要进行代理。这就说明plugin方法必须要返回一个JDK代理对象。

2、plugin方法

由于plugin方法定义在Interceptor接口中,并且上面已经看到了其在接口中的定义,下面看我自定义的接口中plugin的实现,

package cn.com.mybatis.plugins;

import java.util.Properties;

import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds; @Intercepts({@Signature(type=Executor.class,method="query",args= {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})
public class MyInterceptor implements Interceptor{ private Properties properties; /**
* incation 封装了拦截的类,方法,方法参数
*/
@Override
public Object intercept(Invocation invocation) throws Throwable {
// TODO Auto-generated method stub
//执行被拦截的类中的方法 Object o=invocation.proceed();
return o;
} /**
* 返回一个JDK代理对象
*/
@Override
public Object plugin(Object target) {
// TODO Auto-generated method stub
return Plugin.wrap(target, this);
} /**
* 允许在配置文件中配置一些属性即
* <plugin interceptor="">
* <property name ="" value =""/>
* </plugin>
*/
@Override
public void setProperties(Properties properties) {
// TODO Auto-generated method stub
this.properties = properties;
} }

从上面的代码中,看到调用了Plugin.wrap方法,wrap的意思是包装,这里返回的是一个JDK代理对象。且改方法的入参有一个target,这里指的就是statementHandler对象。

2.1、Plugin.wrap方法

我们现在看Plugin类的wrap方法,如下

public static Object wrap(Object target, Interceptor interceptor) {
//1、获得自定义拦截器上的注解信息key为要拦截的接口的Class对象,value为该接口中的Method
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
//2、获得目标类中所有接口中被拦截的接口信息
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
//3、如果存在接口,则使用JDK动态代理,否则返回被代理对象
if (interfaces.length > 0) {
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}

上面已经写上了注释,第一步会获得自定义拦截器上的注解,即下面的内容,

@Intercepts({@Signature(type=Executor.class,method="query",args= {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})
public class MyInterceptor implements Interceptor{}

获得@Intercepts注解的内容,该注解中的值是@Signature注解的一个数组,看下@Intercepts注解的定义,

/**
* Copyright 2009-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ibatis.plugin; import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; /**
* @author Clinton Begin
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Intercepts {
Signature[] value();

很明显必须是一个Signature类型的数组,和配置的注解的内容是一样的,在看@Signature注解的定义,

/**
* Copyright 2009-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ibatis.plugin; import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; /**
* @author Clinton Begin
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Signature {
Class<?> type(); String method(); Class<?>[] args();
}

根据注解中的定义,再对比配置,发现一样,不一样就惨了。

回到wrap方法中,第二步就是要找到被代理对象statementHandler(这里是RoutingStatementHandler)所实现的接口和拦截器中配置的接口,返回被代理对象所实现的接口且配置在拦截器中的接口。

private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
Set<Class<?>> interfaces = new HashSet<Class<?>>();
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()]);
}

第三步就是大名鼎鼎的JDK动态代理了,

//3、如果存在接口,则使用JDK动态代理,否则返回被代理对象
if (interfaces.length > 0) {
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;

如果第二步存在接口则生成一个JDK动态代理,也就是RoutingStatementHandler的代理对象并返回;如果没有则直接返回原对象,也就是RoutingStatementHandler。看第三个参数new Plugin(target,interceptor,signatureMap),下面看Plugin类,

构造方法给target、interceptor、signatureMap进行了赋值。看该类实现了InvocationHandler接口,这类和代理有关系,肯定实现了invoke方法。

2.2、Plugin的invoke方法

从上面的分析知道使用Plugin生成了代理类,那么在执行方法时肯定会调用该类的invoke方法,

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
if (methods != null && methods.contains(method)) {
//1、拦截器的interceptor方法
return interceptor.intercept(new Invocation(target, method, args));
}
return method.invoke(target, args);//2、直接调用方法
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}

从上面可以看到invoke方法主要完成了两项工作,一个就是调用拦截器的intercept方法,第二个就方法的正常调用。主要看intercept函数,由于intercept方法是Interceptor接口中的,下面看我的拦截器中的intercept方法,

/**
* invocation 封装了拦截的类,方法,方法参数
*/
@Override
public Object intercept(Invocation invocation) throws Throwable {
// TODO Auto-generated method stub
//写自己的逻辑,改变参数、监控SQL执行等 //最后一定要执行下面的代码,对原方法的调用
Object o=invocation.proceed();
return o;
}

看intercept方法的参数Invocation,在调用intercept方法时传入了一个Invocation的实例,

return interceptor.intercept(new Invocation(target, method, args));

target表示的是被代理的对象,这里就是RoutingStatementHandler对象,method就是要执行的方法,args是方法的参数。在自定义的拦截器中重写intercept方法时除了加入自己的逻辑处理外,要调用invocation.proceed()方法,此方法是做什么的那,

public Object proceed() throws InvocationTargetException, IllegalAccessException {
return method.invoke(target, args);
}

可以看到此方法是对被代理对象的方法的调用,也就是在执行完自定义逻辑外肯定要执行目标方法,也就是被代理对象的方法。

三、总结

上面就分析完了mybatis拦截器的实现原理,综合拦截器标签<plugin>的解析,以及Interceptor接口中的三个方法,

/**
* Copyright 2009-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ibatis.plugin; import java.util.Properties; /**
* @author Clinton Begin
*/
public interface Interceptor { Object intercept(Invocation invocation) throws Throwable; Object plugin(Object target); void setProperties(Properties properties); }
  • 在解析<plugin>标签时如果有<property>标签,则解析为Proerties对象,并调用setProperties方法设置到拦截器中;
  • mybatis拦截器可以拦截的Executor、StatementHandler、ParameterHandler、ResultSetHandler中的方法,在生成这些接口的实例时如果有拦截,则调用plugin方法,生成一个JDK动态代理对象;
  • 在执行Executor、StatementHandler、ParameterHandler、ResultSetHandler中的方法时,如果自定义拦截器拦截了相应的方法,则调用intercept方法,执行自定义拦截器中的逻辑;

有不正之处,欢迎指正,感谢!

mybatis源码分析:插件是什么的更多相关文章

  1. MyBatis 源码分析 - 插件机制

    1.简介 一般情况下,开源框架都会提供插件或其他形式的拓展点,供开发者自行拓展.这样的好处是显而易见的,一是增加了框架的灵活性.二是开发者可以结合实际需求,对框架进行拓展,使其能够更好的工作.以 My ...

  2. 精尽MyBatis源码分析 - 插件机制

    该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...

  3. MyBatis 源码分析 - 缓存原理

    1.简介 在 Web 应用中,缓存是必不可少的组件.通常我们都会用 Redis 或 memcached 等缓存中间件,拦截大量奔向数据库的请求,减轻数据库压力.作为一个重要的组件,MyBatis 自然 ...

  4. MyBatis 源码分析 - 内置数据源

    1.简介 本篇文章将向大家介绍 MyBatis 内置数据源的实现逻辑.搞懂这些数据源的实现,可使大家对数据源有更深入的认识.同时在配置这些数据源时,也会更清楚每种属性的意义和用途.因此,如果大家想知其 ...

  5. MyBatis 源码分析 - 映射文件解析过程

    1.简介 在上一篇文章中,我详细分析了 MyBatis 配置文件的解析过程.由于上一篇文章的篇幅比较大,加之映射文件解析过程也比较复杂的原因.所以我将映射文件解析过程的分析内容从上一篇文章中抽取出来, ...

  6. MyBatis 源码分析 - 配置文件解析过程

    * 本文速览 由于本篇文章篇幅比较大,所以这里拿出一节对本文进行快速概括.本篇文章对 MyBatis 配置文件中常用配置的解析过程进行了较为详细的介绍和分析,包括但不限于settings,typeAl ...

  7. MyBatis 源码分析系列文章导读

    1.本文速览 本篇文章是我为接下来的 MyBatis 源码分析系列文章写的一个导读文章.本篇文章从 MyBatis 是什么(what),为什么要使用(why),以及如何使用(how)等三个角度进行了说 ...

  8. 精尽MyBatis源码分析 - 文章导读

    该系列文档是本人在学习 Mybatis 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释(Mybatis源码分析 GitHub 地址.Mybatis-Spring 源码分析 GitHub ...

  9. MyBatis源码分析(2)—— Plugin原理

    @(MyBatis)[Plugin] MyBatis源码分析--Plugin原理 Plugin原理 Plugin的实现采用了Java的动态代理,应用了责任链设计模式 InterceptorChain ...

  10. 【MyBatis源码分析】select源码分析及小结

    示例代码 之前的文章说过,对于MyBatis来说insert.update.delete是一组的,因为对于MyBatis来说它们都是update:select是一组的,因为对于MyBatis来说它就是 ...

随机推荐

  1. ffmpeg之视频(avc+aac)无损转mp4(批处理,拖放)

    很多能够无损转视频的工具都来自命令行的ffmpeg版本,本文将介绍如何简单的批处理方法(直接拖放到bat文件上)来实现无损转视频. 工具/原料 ffmpeg(默认的static版本) 方法/步骤   ...

  2. Vue3.0极速入门(二) - 目录和文件说明

    目录结构 以下文件均为npm create helloworld自动生成的文件目录结构 目录截图 目录说明 目录/文件 说明 node_modules npm 加载的项目依赖模块 src 这里是我们要 ...

  3. Java并发编程(一)JUC同步类

    JUC 是学习 Java 并发编程的小伙伴不可避免的一个 pkg,JUC提供了对并发编程的底层支持,比如我们熟悉的线程池.MQ.线程同步... 都有JUC的影子,下面我们一起来看看JUC下比较重要的几 ...

  4. vsftp配置使用

    vsftp简介: VSFTP是一个基于GPL发布的类Unix系统上使用的FTP服务器软件,它的全称是Very Secure FTP 从此名称可以看出来,编制者的初衷是代码的安全. 安全性是编写VSFT ...

  5. itestwork(爱测试)开源一站式接口测试&敏捷测试工作站 9.0.0 RC2 发布,重大升级

    (一)itest 简介 itest work (爱测试)  一站式工作站让测试变得简单.敏捷.itest work 包含极简的任务管理,测试管理,缺陷管理,测试环境管理,接口测试,接口Mock 6合1 ...

  6. 「C++」深度分析C++中i++与++i的区别

    大家好,我是Charzie.在C++编程中,i++和++i是两个常见的自增运算符,用于将变量的值增加1(有时与i+=1效果一样).然而,虽然它们的功能看似相似,但在实际使用中却存在显著的区别.本博客将 ...

  7. CF1838A-Blackboard-List

    题意简述 在黑板上有两个数字,进行如下操作 \(n-2\) 次: 每次在黑板上选择任意两个数,将两个数的差的绝对值写在黑板上. 这样你会得到一个长度为 \(n (3 \le n \le 100)\) ...

  8. C#.NET Winform使用线程承载WCF (硬编码配置)

    winform同步承载WCF时,遇到大量请求,可能会阻塞UI线程.这时就需要开个线程来承载WCF. 1.硬编码形式创建WCF服务,WCFServer类: using CommonUtils; usin ...

  9. POJ2247,hdu1058(Humble Numbers)

    Problem Description A number whose only prime factors are 2,3,5 or 7 is called a humble number. The ...

  10. 用pm2命令管理你的node项目

    文章目录 前言 安装 运行项目 pm2的命令 前言 我在服务器上运行node项目,使用命令nohup npm start &,结果关闭终端之后,进程就会停止,看来nohup也不是万能的后台运行 ...