Mybatis的插件解读

619 阅读4分钟

深入浅出Mybatis的插件原理

插件的作用:

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

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)

mybatis 的plugin只能对上述类中的方法进行拦截

插件的应用场景
  1. 记录执行的sql语句.以及参数
  2. 改造sql语句,如分页.根据指定的时间参数查询不同的数据表
  3. 对sql语句进行检测,如sql中不允许使用like '%%';
  4. 分库分表查询

如何使用插件

配置xml文件
<!-- mybatis-config.xml -->
<plugins>
  <plugin interceptor="org.mybatis.example.ExamplePlugin">
    <property name="someProperty" value="100"/>
  </plugin>
</plugins>
创建拦截器
// ExamplePlugin.java
@Intercepts({@Signature(
  type= Executor.class,
  method = "update",
  args = {MappedStatement.class,Object.class})})
public class ExamplePlugin implements Interceptor {
  private Properties properties = new Properties();
 
 
 // 拦截到具体方法执行
 public Object intercept(Invocation invocation) throws Throwable {
    // implement pre processing if need
    Object returnObject = invocation.proceed();
    // implement post processing if need
    return returnObject;
  }
  
  // 自定义属性
  public void setProperties(Properties properties) {
    this.properties = properties;
  }
}


插件的原理

1插件解析过程

全局Configuration对象创建来自于SqlSessionFactory对象的创建;跟踪源码入口SqlSessionFactoryBuilder.build()

private void pluginElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        String interceptor = child.getStringAttribute("interceptor");
        Properties properties = child.getChildrenAsProperties();
        Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();
        interceptorInstance.setProperties(properties);
        configuration.addInterceptor(interceptorInstance);
      }
    }
  }

  1. 获取全局配置文件定义的插件,实例化.设置自定义属性
  2. 加入到Configuration的拦截器链中
拦截器链
public class InterceptorChain {

  private final List<Interceptor> interceptors = new ArrayList<>();

// 生成代理对象的流程
  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);
  }

}

2 如何将插件植入到具体对象中
  1. 装饰器
  2. 代理

mybatis的插件基于jdk的动态代理来实现;

Executor 执行器
  1. 执行器的创建
Configuration 

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;
}
  • 执行器类型存在三种类型SIMPLE, REUSE, BATCH

  • 默认执行器类型为Simple

    我们可以看到CachingExecutor创建后经过PluginAll方法返回新的执行器对象

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

    进入Interceptor.plugin方法中

    public interface Interceptor {
    
      Object intercept(Invocation invocation) throws Throwable;
    
    // 默认方法
      default Object plugin(Object target) {
        return Plugin.wrap(target, this);
      }
    
      default void setProperties(Properties properties) {
        // NOP
      }
    
    }
    
    

    然后进入到MyBatis给我们提供的Plugin工具类的实现 wrap方法中。

    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) {
      // 获取执行器上的配置的Intercepts注解信息,以及其拦截的方法
        Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
        Class<?> type = target.getClass();
        // 获取实现的接口 需要是signatureMap的key
        Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
        //如果实现了接口 则创建代理对象,否则返回原生对象
        if (interfaces.length > 0) {
          return Proxy.newProxyInstance(
              type.getClassLoader(),
              interfaces,
              new Plugin(target, interceptor, signatureMap));
        }
        return target;
      }
    
    
    // 解析Intercepts注解
      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<>();
        for (Signature sig : sigs) {
          Set<Method> methods = MapUtil.computeIfAbsent(signatureMap, sig.type(), k -> new HashSet<>());
          try {
          // 获取Executor .StatementHandler.ParamterHandler.ResultSetHandler中的所有public方法
            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<>();
        while (type != null) {
          for (Class<?> c : type.getInterfaces()) {
          // 
            if (signatureMap.containsKey(c)) {
              interfaces.add(c);
            }
          }
          type = type.getSuperclass();
        }
        return interfaces.toArray(new Class<?>[0]);
      }
    
    }
    
    
    1. 解析拦截器上signature注解,并检查是否符合规范
    2. 创建代理对象$Proxy

    插件顺序为Plugin1->Plugin2->Plugin3->Plugin4,但是执行顺序为Plugin4->Plugin3->Plugin2->Plugin1->Executor

未命名文件 (5).png

插件的执行过程
  1. 执行器执行具体方法 如Executor.query();基于创建的代理对象进入Plugin的invoker方法中
Plugin 

@Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
        // 获取拦截器interceptor类上标注的Signature注解中需要拦截的方法
      Set<Method> methods = signatureMap.get(method.getDeclaringClass());
        // 若存在则执行intercept 方法
      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);
    }
  }


以Executor的query方法为例

未命名文件 (6).png

class作用
Plugin实现了InvocationHandler,执行代理
Interceptor拦截器,执行指定方法拦截
Invocation对被代理类进行包装,可以调用proceed()调用到被拦截的方法

最后;插件的原理虽然简单,但真正要用好它,需要对mybatis的各个类之间的关系要有足够的了解,否则你的插件不仅不会生效,还会影响到mybatis的执行流程;

自定义拦截器实现数据脱敏

因为我们需要对返回的数据进行脱敏,所以我们拦截的对象为ResultSetHandler;

辅助注解
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Desensitization {

    /**
     * 从0起始
     *
     * @return
     */
    int startOffset();

    /**
     * 结束
     *
     * @return
     */
    int endOffset();

    /**
     * 占位符
     *
     * @return
     */
    char replaceChar() default '*';
}

编写拦截器
@Intercepts({@Signature(
        type = ResultSetHandler.class,
        method = "handleResultSets",
        args = {Statement.class})})
public class DesensitizationInterceptor implements Interceptor {


    public static final Map<Class, Map<Field, Desensitization>> fieldMap = new ConcurrentHashMap<>(64);


    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // implement pre processing if need List
        Object returnObject = invocation.proceed();
        List c = (List) returnObject;
        for (Object o : c) {
            desensitization(o);
        }
        // implement post processing if need
        return returnObject;
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {

    }


    public void desensitization(Object returnObject) {
        if (returnObject != null && returnObject instanceof Account) {
            Account account = (Account) returnObject;
            // 转换参数
            Class<? extends Account> aClass = account.getClass();
            Map<Field, Desensitization> fieldDesensitizationMap = fieldMap.get(aClass);
            if (fieldDesensitizationMap == null) {
                Map<Field, Desensitization> fieldDesensitizationNew = new ConcurrentHashMap<>();
                fieldDesensitizationMap = fieldMap.computeIfAbsent(aClass, e -> {
                    return fieldDesensitizationNew;
                });
            }
            Field[] declaredFields = aClass.getDeclaredFields();
            for (Field field : declaredFields) {
            // 仅对字符串进行处理,其他属性不做处理
                if (field.getType() != String.class) {
                    continue;
                }
                Desensitization annotation = null;
                if (!fieldDesensitizationMap.containsKey(field)) {
                    annotation = field.getAnnotation(Desensitization.class);
                    fieldDesensitizationMap.putIfAbsent(field, annotation);
                } else {
                    annotation = fieldDesensitizationMap.get(field);
                }
                if (annotation == null) {
                    continue;
                }
                if (!field.isAccessible()) {
                    field.setAccessible(true);
                }
                try {
                    String s = (String) field.get(returnObject);
                    if (StringUtils.isEmpty(s)) {
                        continue;
                    }
                    // 替换参数
                    char[] chars = s.toCharArray();
                    for (int i = 0; i < chars.length; i++) {
                        if (i >= annotation.startOffset() && i <= annotation.endOffset()) {
                            chars[i] = annotation.replaceChar();
                        }
                    }
                    // 重置属性值
                    field.set(returnObject,new String(chars));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }

            }
        }
    }
}

以上代码仅供参考;