mybatis插件原理解析及自定义插件实践(代码片段)

敲代码的小小酥 敲代码的小小酥     2023-03-02     133

关键词:

一、插件原理解析

首先,要搞清楚插件的作用。不管是我们自定义插件,还是用其他人开发好的第三方插件,插件都是对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;
  

//执行代理对象的代理方法
  @Override
  public 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 #251
    if (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 
    @Override
    public Object intercept(Invocation invocation) throws Throwable 
        System.out.println("拦截的目标对象:"+invocation.getTarget());
        System.out.println("写加强逻辑");
        return invocation.proceed();//执行原方法
    

    @Override
    public Object plugin(Object target) 
        //生成代理对象
        return Plugin.wrap(target,this);
    

    @Override
    public void setProperties(Properties properties) 
        System.out.println("获取配置文件参数");
    

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

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

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

@Configuration
public class MybatisPluginsBean 
    @Bean
    public 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 
    @Override
    public Object intercept(Invocation invocation) throws Throwable 
        //执行原方法
        List<Object> records = (List<Object>)invocation.proceed();
        //TODO 对返回值records进行加密处理
        return records;//执行原方法
    

    @Override
    public Object plugin(Object target) 
        //生成代理对象
        return Plugin.wrap(target,this);
    

    @Override
    public void setProperties(Properties properties) 
        System.out.println("获取配置文件参数");
    

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

深入浅出mybatis:mybatis插件及开发过程

本篇文章是「深入浅出MyBatis:技术原理与实践」书籍的总结笔记。上一篇介绍了MyBatis解析和运行原理,包括SqlSessionFactory的构建和SqlSession的执行过程,其中,SqlSession包含四大对象,可以在四大对象调度的时候插入自定义的代码... 查看详情

mybatis源码分析插件实现原理

MyBatis插件原理----从<plugins>解析开始本文分析一下MyBatis的插件实现原理,在此之前,如果对MyBatis插件不是很熟悉的朋友,可参看此文MyBatis7:MyBatis插件及示例----打印每条SQL语句及其执行时间,本文我以一个例子说明了MyBatis... 查看详情

mybatis插件机制原理

mybatis插件机制及分页插件原理参考链接:mybatis插件机制及分页插件原理如何编写一个自定义mybatis插件参考链接:mybatis自定义插件的使用 查看详情

mybatis插件机制源码解析(代码片段)

引言本篇源码解析基于mybatis3.5.8版本。首先需要说明的是,本篇文章不是mybatis插件开发的教程,而是从源码层面分析mybatis是如何支持用户自定义插件开发的。mybatis的插件机制,让其扩展能力大大增加。比如我们项目... 查看详情

自定义实现webpack插件原理解析(代码片段)

webpack插件构成部分一个具名javascript函数在他的原型上定义apply方法指定一个触及到webpack本身的事件钩子操作webpack内部的特定实例数据 实现功能后调用webpack提供的callback函数webpack基本架构插件有一个构造函数实例化出来,构造... 查看详情

自定义实现webpack插件原理解析(代码片段)

webpack插件构成部分一个具名javascript函数在他的原型上定义apply方法指定一个触及到webpack本身的事件钩子操作webpack内部的特定实例数据 实现功能后调用webpack提供的callback函数webpack基本架构插件有一个构造函数实例化出来,构造... 查看详情

mybatis源码分析-插件机制(代码片段)

...3.2执行插件逻辑4.实现一个分页插件5.总结序号内容链接1MyBatis源码分析-MyBatis入门https://thinkwon.blog.csdn.net/article/details/1148088522MyBatis源码分析-配置文件解析过程https://thinkwon.blog.csdn.net/article/details/1148089623MyBatis源码分析-映射文件解... 查看详情

mybatis插件原理和pagehelper结合实战分页插件

今天和大家分享下mybatis的一个分页插件PageHelper,在讲解PageHelper之前我们需要先了解下mybatis的插件原理。PageHelper的官方网站:https://github.com/pagehelper/Mybatis-PageHelper一、Plugin接口mybatis定义了一个插件接口org.apache.ibatis.plugin.Intercepto... 查看详情

七:mybatis逆向工程&分页插件(代码片段)

目录1、分页插件Mybatis插件典型适用场景分页功能公共字段统一赋值性能监控其它实现思考:自定义分页插件分页插件使用代理和拦截是怎么实现的?PageHelper原理2、MyBatis逆向工程1、分页插件MyBatis通过提供插件机制,... 查看详情

mybatis插件原理(代码片段)

目录插件的使用拦截器介绍及配置源码分析总结MyBatis提供了一种插件(plugin)的功能,虽然叫做插件,但其实这是拦截器功能。那么拦截器拦截MyBatis中的哪些内容呢?我们进入官网看一看:MyBatis允许你在已映射语... 查看详情

如何编写一个webpack的插件原理及实践(代码片段)

_阅读目录一:webpack插件的基本原理二:理解Compiler对象和Compilation对象三:插件中常用的API四:编写插件实战回到顶部一:webpack插件的基本原理webpack构建工具大家应该不陌生了,那么下面我们来简单的了解下什么是webpack的插件... 查看详情

深入浅出mybatis:mybatis解析和运行原理

...在运行时创建代理对象,做一些特殊的处理。本篇会介绍MyBatis解析和运行原理,下一篇介绍插件及应用,目的是更好地编写插件,通过本篇的介绍,你会了解到:构建SqlSessionFactory过程映射器的动态代理SqlSession的4大对象sql执行... 查看详情

mybatis自定义插件机制分析(源码级剖析)(代码片段)

**问题:什么是Mybatis插件?有什么作用?**一般开源框架都会提供扩展点,让开发者自行扩展,从而完成逻辑的增强。基于插件机制可以实现了很多有用的功能,比如说分页,字段加密,监控等功能,这种通用的功能,就如同AOP... 查看详情

mybatis之插件原理

鲁春利的工作笔记,好记性不如烂笔头转载自:深入浅出Mybatis-插件原理Mybatis采用责任链模式,通过动态代理组织多个拦截器(插件),通过这些拦截器可以改变Mybatis的默认行为(诸如SQL重写之类的),由于插件会深入到Mybatis... 查看详情

jquery幻灯片插件owlcarousel(代码片段)

简介OwlCarousel是一个强大、实用但小巧的jQuery幻灯片插件,它具有一下特点:兼容所有浏览器支持响应式支持 CSS3 过度支持触摸事件支持 JSON 及自定义 JSON 格式支持进度条支持自定义事件支持延迟加载支持... 查看详情

mybatis之插件(代码片段)

...插件二、扫描三、封装四、执行五、应用一、自定义插件mybatis的插件是代理模式与责任链模式的结合。每一个插件以责任链并进行封装,都是一层对Executor的代理@Intercepts(@Signature(type=Executor.class,method="query",... 查看详情

mybatis之插件(代码片段)

...插件二、扫描三、封装四、执行五、应用一、自定义插件mybatis的插件是代理模式与责任链模式的结合。每一个插件以责任链并进行封装,都是一层对Executor的代理@Intercepts(@Signature(type=Executor.class,method="query",... 查看详情

mybatis插件原理(代码片段)

目录插件的使用拦截器介绍及配置源码分析总结MyBatis提供了一种插件(plugin)的功能,虽然叫做插件,但其实这是拦截器功能。那么拦截器拦截MyBatis中的哪些内容呢?我们进入官网看一看:MyBatis允许你在已映射语... 查看详情