mybatis 源码 | mybatis 插件及动态代理的使用

开头说两句

Java 基础 Demo 站: https://www.javastudy.cloud
Java 中高级开发博客: https://www.lixiang.red
Java 学习公众号: Java 技术大本营
java_subscribe

学习背景

​ 最近公司在做一些数据库安全方面的事情,如数据库中不能存手机号明文,不能存身份证号明文, 但是项目已经进行了好几个月了, 这时候在应用层面去改显然不太现实, 所以就有了 MyBatis 的自定义插件就出场了!

插件知识点总述

  1. MyBatis 的插件,使用拦截器链的方式调用,其代码抽象如下所示 org.apache.ibatis.plugin.InterceptorChain

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

    不停的把 target 传入到插件的 plugin 方法中, 让插件对参数/返回值/语句/执行器做修改,如下图所示

    4Z3WRd

  2. 使用动态代理的方式调用插件功能

    这是一个通用的思路,在想对一个原有的方法/功能进行加强的时候,首要的思路就是使用代理, 然后采用如下的代码格式来加强

    beforeInvoke();//在调用之前加强
    proxy.invoke();//原来的逻辑
    afterInvoke();// 在调用之后加强
    

    MyBatis 的动态代理也是如此,关键代码如下 org.apache.ibatis.plugin.Plugin

    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);
    
  3. 可以单独拿出来脱离 MyBatis 框架使用,小刀觉得, 这一点才是 MyBatis 插件的精华所在

    这一点很关键,不要被 MyBatis 框架给局限了,如下面的测试类,还可以使用 MyBatis 的插件去拦截 HashMap 的 get 方法,这样就可以提炼一个小型的 AOP 了。

插件的使用及原理

在这里我们用 MyBatis 的官方测试代码为例:

/**
 *单元测试类
 */
@Test
  void mapPluginShouldInterceptGet() {
    Map map = new HashMap();
    map = (Map) new AlwaysMapPlugin().plugin(map);
    assertEquals("Always", map.get("Anything"));
  }
/**
 *插件类,我们在自己的项目中也应该建这样的类
 */
  @Intercepts({@Signature(type = Map.class, method = "get", args = {Object.class})})
  public static class AlwaysMapPlugin implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) {
      return "Always";
    }
  }

这份代码中要注意以下几个地方

MyBatis 插件在源码中的调用流程

这一段是为了大家方便在源码中查看插件是从哪里开始,然后到哪里执行。对整体有个印象

  1. 从 XML 中读取插件配置,并加入到插件链中

    org.apache.ibatis.builder.xml.XMLConfigBuilder.parseConfiguration
    
    pluginElement(root.evalNode("plugins"));
    
  2. 在获取 ParameterHandler/ResultSetHandler/StatementHandler/Executor 的时候调用插件链

    org.apache.ibatis.session.Configuration
    
    public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
        ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
      // 在这里调用
        parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
        return parameterHandler;
      }
    
    
  3. pluginAll() 方法中如上所述生成代理对象,把我们自定义的加强逻辑给加上去。 然后在执行 MyBatis 的 mapper 的时候,就可以走我们插件里面逻辑了

总结

MyBatis 源码并不难,整理好逻辑好一步步的跟踪下去就可以了,建议阅读本文时把 MyBatis 源码也打开,跟着一起看,紧紧抓住动态代理的实现就肯定会把这个弄清的

  • MyBatis

    MyBatis 本是 Apache 软件基金会 的一个开源项目 iBatis,2010 年这个项目由 Apache 软件基金会迁移到了 Google code,并且改名为 MyBatis ,2013 年 11 月再次迁移到了 GitHub。

    148 引用 • 404 回帖 • 694 关注

赞助商 我要投放

回帖
请输入回帖内容 ...