栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Mybatis源码解读-插件

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Mybatis源码解读-插件

插件允许对Mybatis的四大对象(Executor、ParameterHandler、ResultSetHandler、StatementHandler)进行拦截

问题

Mybatis插件的注册顺序与调用顺序的关系?

使用

在讲源码之前,先看看如何自定义插件。

mybatis-demo,官方文档

  1. 创建插件类

    自定义插件类需要实现Interceptor

    // 注解配置需要拦截的类以及方法
    @Intercepts({
            @Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class})
    })
    // 实现Interceptor接口
    public class SqlLogPlugin implements Interceptor {
    
        
        @Override
        public Object intercept(Invocation invocation) throws Throwable {
            long begin = System.currentTimeMillis();
            try {
                return invocation.proceed();
            } finally {
                long time = System.currentTimeMillis() - begin;
                System.out.println("sql 运行了 :" + time + " ms");
            }
        }
    
        
        
    
        
        @Override
        public void setProperties(Properties properties) {
            // 这是xml中配置的参数
            properties.forEach((k, v) -> {
                System.out.printf("SqlLogPlugin---key:%s, value:%s%n", k, v);
            });
        }
    }
    
  2. 注册

    在配置文件注册插件

    
        
            
            
        
    
    
  3. 效果

    控制输出

    SqlLogPlugin---key:key1, value:root
    SqlLogPlugin---key:key2, value:123456
    sql 运行了 :17 ms
    
源码

原理:Mybatis四大对象创建时,都回去判断是否满足插件的拦截条件,满足,则四大对象就会被Plugin类代理

源码分3部分讲。注册、包装、调用

  1. 注册

    xml方式的注册,是在XMLConfigBuilder#pluginElement完成的。

    不明觉厉的同学,请参考上一篇文章:Mybatis源码解读-配置加载和Mapper的生成

    // XMLConfigBuilder#pluginElement(XNode parent)
    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);
        }
      }
    }
    

    configuration.addInterceptor做得操作很简单

  2. 包装

    上面讲了插件的注册,最后调用的是configuration.addInterceptor,最终调用的是InterceptorChain#addInterceptor

    public class InterceptorChain {
    
      private final List interceptors = new ArrayList<>();
      
      public Object pluginAll(Object target) {
        for (Interceptor interceptor : interceptors) {
          // 调用每个插件的plugin方法,判断是否需要代理
          target = interceptor.plugin(target);
        }
        return target;
      }
      // 将拦截器添加interceptors集合中存起来
      public void addInterceptor(Interceptor interceptor) {
        interceptors.add(interceptor);
      }
    
      public List getInterceptors() {
        return Collections.unmodifiableList(interceptors);
      }
    
    }
    

    我们案例是拦截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之后,会调用InterceptorChain的pluginAll方法
      statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
      return statementHandler;
    }
    

    那么我们再仔细分析下pluginAll方法,pluginAll调用的是每个插件的plugin方法

    default Object plugin(Object target) {
      return Plugin.wrap(target, this);
    }
    

    可以看到,最终调用的是Plugin.*wrap*

    public static Object wrap(Object target, Interceptor interceptor) {
      // 获取插件的拦截信息(就是获取@Intercepts注解的内容)
      Map, Set> signatureMap = getSignatureMap(interceptor);
      Class type = target.getClass();
      // 判断是否满足拦截条件
      Class[] interfaces = getAllInterfaces(type, signatureMap);
      if (interfaces.length > 0) {
        // 满足拦截条件则返回Plugin代理对象
        return Proxy.newProxyInstance(
            type.getClassLoader(),
            interfaces,
            new Plugin(target, interceptor, signatureMap));
      }
      // 不满足则返回原对象
      return target;
    }
    
  3. 调用

    在上一个包装步骤提到,满足条件会返回代理对象,即调用StatementHandler的所有方法,都会经过Plugin的invoke方法,去看看

    // Plugin#invoke
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      try {
        // 获取拦截条件(需要拦截的方法)
        Set methods = signatureMap.get(method.getDeclaringClass());
        if (methods != null && methods.contains(method)) {
          // 满足拦截条件,则调用插件的intercept方法
          return interceptor.intercept(new Invocation(target, method, args));
        }
        return method.invoke(target, args);
      } catch (Exception e) {
        throw ExceptionUtil.unwrapThrowable(e);
      }
    }
    
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/891813.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号