一、插件原理解析

首先,要搞清楚插件的作用。不管是我们自定义插件,还是用其他人开发好的第三方插件,插件都是对MyBatis的四大核心组件:Executor,StatementHandler,ParameterHandler,ResultSetHandler来进行增强的,利用动态代理的技术,来增强框架的方法,来满足我们特殊的业务需求。

1.先看几个重要的类:

package org.apache.ibatis.plugin;import java.util.Properties;/*** @author Clinton Begin*//**所有的插件,都要实现这个接口。
*/
public interface Interceptor {//这个方法写增强逻辑Object intercept(Invocation invocation) throws Throwable;//生成代理对象,生成四大核心组件的代理类。通过Plugin.warp(target,this)来生成Object plugin(Object target);
//读取MyBatis配置文件中定义插件拦截器时配置的一些属性void setProperties(Properties properties);}

再看intercept方法中参数对象Invocation源码:

package org.apache.ibatis.plugin;import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;/*** @author Clinton Begin*/
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);}}

上面提到,插件使用的是jdk动态代理技术,MyBatis定义了Plugin类实现了InvocationHandler接口,看源码:

package org.apache.ibatis.plugin;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;import org.apache.ibatis.reflection.ExceptionUtil;/*** @author Clinton Begin*/
public class Plugin implements InvocationHandler {private final Object target;private final Interceptor interceptor;//解析Interceptor插件实现类的@Signature注解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) {Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);Class<?> type = target.getClass();Class<?>[] interfaces = getAllInterfaces(type, signatureMap);if (interfaces.length > 0) {//JDK动态代理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 {//获取@Signature注解中method的值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);}}
//解析@Signature参数private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);// issue #251if (interceptsAnnotation == null) {throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());      }Signature[] sigs = interceptsAnnotation.value();Map<Class<?>, Set<Method>> signatureMap = new HashMap<Class<?>, Set<Method>>();for (Signature sig : sigs) {Set<Method> methods = signatureMap.get(sig.type());if (methods == null) {methods = new HashSet<Method>();signatureMap.put(sig.type(), methods);}try {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;}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()]);}}

可见,Plugin类的wrap方法生成代理类,invoke方法执行代理方法。Plugin中有解析@Signature,看@Signature的源码:

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();
//要拦截的方法,type定义的哪个组件,这里就要写该组件里对应的要加强的方法String method();//method所写方法的参数,可以定位到重载方法的某一个Class<?>[] args();
}

2.开发插件基本套路
介绍了插件有关的几个重要的类,下面就用这几个类,演示一下插件开发的基本套路。
首先,定义插件类,实现Interceptor接口,并定义@Signature 说明要拦截的类和方法,如下:

@Intercepts(@Signature(type = Executor.class,//拦截Executor类method = "update",//拦截update方法args = {MappedStatement.class,Object.class} //update方法的参数)
)
public class MyBatisPlugin implements Interceptor {@Overridepublic Object intercept(Invocation invocation) throws Throwable {System.out.println("拦截的目标对象:"+invocation.getTarget());System.out.println("写加强逻辑");return invocation.proceed();//执行原方法}@Overridepublic Object plugin(Object target) {//生成代理对象return Plugin.wrap(target,this);}@Overridepublic void setProperties(Properties properties) {System.out.println("获取配置文件参数");}
}

然后,将该插件注入Spring容器中,即可。如果是spring项目,可以在xml中定义plugins插件,如下:

 <plugins><plugin interceptor="com.github.pagehelper.PageInterceptor"/></plugins>

如果是SpringBoot项目,如下注入:

@Configuration
public class MybatisPluginsBean {@Beanpublic MyBatisPlugin myBatisPlugin(){return new MyBatisPlugin();}
}

由上可见,定义插件只需要两个关键步骤,即定义Interceptor实现类,实现插件逻辑;注入到spring容器中这两步即可。

二、PageHelper插件原理解析

我们结合PageHelper插件,研究一下插件的执行原理。
首先,读取xml文件,并解析plugins标签,然后将配置的interceptor类通过反射生成对象,注入到Spring容器中。这个步骤的代码我们省去,下面看生成插件对象后,执行了哪些操作。
首先,生成插件对象后,在Configuration类中,将插件对象加入到了拦截链interceptorChain中:

然后看一下,PageInterceptor类,是对Executor组件的query方法进行了拦截:

那么何时对Executor类进行的增强呢,我们知道,插件的原理是代理,那么代理对象何时生成呢,一定是在创建Executor对象的时候,生成代理对象,总不能先生成元对象,执行了一半儿,再生成代理对象吧,那容器中岂不是有了两个Executor对象,破坏了单例规则吗,对吧。所以看Executor创建的地方:
Executor是在SqlSession创建的时候被创建的,SqlSession是在SqlSessionFactory中的openSession方法中创建的,所以从openSession中点进去,查看,最终点到:


看pluginAll方法:

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

可见,遍历拦截链,然后调用plugin方法,获得代理对象。interceptor的plugin最终会调用Plugin的wrap方法,该方法在第一部分已经分析过,最终,Executor就返回了代理对象。
那么在接下来执行Executor方法的时候,就会调用Plugin类的invoke方法,如果是加强的方法,则走PageInterceptor的intercept方法,进行分页增强,如果没有拦截的方法,则执行原方法即可。这就是分页插件的执行流程。
**总结:**由上面的流程分析可以知道,插件对象是在四大组件的类生成时,生成的代理对象。通过Configuration中的拦截链属性,判断是否要生成代理对象。而代理对象方法执行,由Plugin类来完成。我们只需定义Interceptor的实现类,来做逻辑代码,而插件的原生方法还是加强方法的执行,是由Plugin类支撑的,也就是Plugin是Interceptor的底层实现。

三、自定义插件实践

1.屏蔽敏感字插件实现
在某些业务场景中,涉及到一些敏感字,需要用*号代替。比如身份证号,手机号中间部分,都要用星号代替,而不是展示全部。我们正常的实现思路是查询出数据后,再做处理,把有身份证号的模块做一下处理,再把有手机号的模块做一下处理。假如这种模块很多的话,那我们需要改很多代码。
这个问题,就可以用插件的方式解决。屏蔽关键字,是对查询结果的处理,在MyBatis中对应的就是ResultSetHandler组件。用插件生成ResultSetHandler的代理对象,在ResultSet中对字段进行屏蔽,不就避免了按功能模块进行屏蔽的繁琐了吗 。
实现思路:
对于开发插件而言,只需关注两步,1.实现Interceptor接口,写插件逻辑 2.注册插件。
首先,拦截ResultSetHandler类的handleResultSets方法,该方法对返回值进行处理。

@Intercepts(@Signature(type = ResultSetHandler.class,method = "handleResultSets",args = {Statement.class} )
)
public class MyBatisPlugin implements Interceptor {@Overridepublic Object intercept(Invocation invocation) throws Throwable {//执行原方法List<Object> records = (List<Object>)invocation.proceed();//TODO 对返回值records进行加密处理return records;//执行原方法}@Overridepublic Object plugin(Object target) {//生成代理对象return Plugin.wrap(target,this);}@Overridepublic void setProperties(Properties properties) {System.out.println("获取配置文件参数");}
}

至于TODO部分,就是加密逻辑,这个我们自己实现即可,这里不过多描述。可以自定义注解,有这个注解的字段,就进行加密处理。
2.分表功能实现
见日常开发总结模块

MyBatis插件原理解析及自定义插件实践相关推荐

  1. 写过Mybatis插件?那说说自定义插件是如何加载的吧?

    大多数框架,都支持插件,用户可通过编写插件来自行扩展功能,Mybatis也不例外. 我们从插件配置.插件编写.插件运行原理.插件注册与执行拦截的时机.初始化插件.分页插件的原理等六个方面展开阐述. 1 ...

  2. Android插件化原理解析——ContentProvider的插件化

    目前为止我们已经完成了Android四大组件中Activity,Service以及BroadcastReceiver的插件化,这几个组件各不相同,我们根据它们的特点定制了不同的插件化方案:那么对于Co ...

  3. Android 插件化原理解析——Service的插件化

    在 Activity生命周期管理 以及 广播的管理 中我们详细探讨了Android系统中的Activity.BroadcastReceiver组件的工作原理以及它们的插件化方案,相信读者已经对Andr ...

  4. android黑科技系列——微信抢红包插件原理解析和开发实现

    一.前言 自从几年前微信添加抢红包的功能,微信的电商之旅算是正式开始正式火爆起来.但是作为Android开发者来说,我们在抢红包的同时意识到了很多问题,就是手动去抢红包的速度慢了,当然这些有很多原因导 ...

  5. 插件化原理解析——ContentProvider的插件化

    目前为止我们已经完成了Android四大组件中Activity,Service以及BroadcastReceiver的插件化,这几个组件各不相同,我们根据它们的特点定制了不同的插件化方案:那么对于Co ...

  6. Mybatis逻辑分页原理解析RowBounds

    物理分页Mybatis插件原理分析(三)分页插件 Mybatis提供了一个简单的逻辑分页使用类RowBounds(物理分页当然就是我们在sql语句中指定limit和offset值),在DefaultS ...

  7. mybatis延迟加载原理解析

    延迟加载前言: 在很多真实的实战的业务场景中,由于业务的复杂度,都会让我们进行过多的进行一些连接查询,在数据量少的时候,我们或许感受不到查询给我们带来的效率影响,在数据量和业务复杂的时候我们进行过多的 ...

  8. Vue插件_自己封装插件_以及使用自定义插件---vue工作笔记0017

    然后我们再来看插件的使用. vue官网上也提供了,怎么样来自己开发插件. 我们自己开发一个插件,并且使用 首先我们把插件包括在一个函数中去, (function(){ })()

  9. 自定义实现webpack插件原理解析

    webpack插件构成部分 一个具名javascript函数 在他的原型上定义apply方法 指定一个触及到 webpack本身的事件钩子 操作webpack内部的特定实例数据 实现功能后调用 web ...

最新文章

  1. 虚幻引擎4:打造街机经典游戏学习教程 Unreal Engine 4: Create an Arcade Classic
  2. python正则表达式代码_python的re正则表达式实例代码
  3. 阿里工程师总结的《MySQL 笔记高清 PDF 》 开放下载
  4. 【NLP】如何清理文本数据
  5. dijkstra算法原理_这 10 大基础算法,程序员必知必会!
  6. oracle行列转换总结
  7. IPSEC ***两个阶段的协商过程
  8. python学习与数据挖掘_Python学习之数据挖掘(三)
  9. vijos训练之——星辰大海中闪烁的趣题
  10. 整理一些质量不错的教程、博客、论坛
  11. C语言main函数带参数在VC6下的调试方法
  12. 实验二木马分析(控制分析)实验和实验三冰河木马实验
  13. CDN技术原理以及所用技术介绍
  14. mysql pxc集群 原理 (图解+秒懂+史上最全)
  15. linux sz命令详解,Linux rz和sz命令详解
  16. cmd复制文件到其他目录
  17. 小程序之跨平台黑魔法
  18. 教你在3GPP官网下载协议
  19. h5通过当前时间获取农历日期
  20. 算法一:递归(包含Hanoi问题、N皇后问题、逆波兰表达式、爬楼梯、放苹果、全排列)

热门文章

  1. openpyxl库操作excel表格
  2. php中文学习网的教程怎么样,PHP下载google相册到本地
  3. 湖南省普通高等学校计算机应用水平,湖南省普通高等学校非计算机专业学生计算机应用水平等...
  4. hookZz,Dobby,xHook,consoleDebugger
  5. Java技术图谱!java高级工程师工资一般多少
  6. C语言折半查找算法及代码实现
  7. 可以辨识艺术品的地图 Google Lens运用AI让机器学会看图说话
  8. EE架构|国内主流OEM的中央计算+区域控制架构信息梳理
  9. 跟原力一起玩转EOS源码-Push Transaction机制
  10. PDF怎么修改,如何调整PDF页面大小