MyBatis插件小知识点

MyBatis插件机制解析
本文详细介绍了MyBatis插件的工作原理及其在实际应用中的优化方式。通过动态代理机制,MyBatis允许开发者轻松地实现诸如慢SQL记录、性能监控等功能。

title: MyBatis插件小知识点 tags:

  • mybatis
  • plugin
  • interceptor
  • proxy categories: mybatis date: 2017-12-12 13:08:58

背景

mybatis作为一款十分优秀的orm框架在大量的互联网应用中得到使用。其提供了比较完善的插件扩展机制

通常我们会使用插件做到许多共通的事情 比如

  1. 慢sql记录
  2. sql性能记录
  3. db主从
  4. 分页
  5. 乐观锁等

那么简单剖析一下mybatis的插件机制

分析

从本质上来说这也仍然是一个动态代理的过程。一般来说我们可以采用java proxy或者aspectj等来实现

mybatis的插件同样是利用了java的proxy来实现

一个典型的mybatis插件会实现Interceptor类 但是整个故事是从Plugin开始的

来简单看一下Plugin

    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) {
        Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
        Class<?> type = target.getClass();
        Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
        if (interfaces.length > 0) {
          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 {
          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);
        }
      }
     
      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实现了InvocationHandler 这个接口提供的invoke方法即使真实调用处

通常我们可以在invoke方法根据对应method等等来实现我们的业务逻辑

我们了解到Mybatis的plugin对于符合signature的方法会调用对应interceptor的intercept方法 这也是我们最熟知的业务逻辑

该处会根据签名来匹配合适的方法来使用特定的interceptor

当查找到指定的interceptor会传入invocation

    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);
      }
     
    }
复制代码

而invocation的proceed就是调用对应的业务方法【如果还有拦截器将会找到下一个拦截器】===》这边有个前提需要拦截器最终调用invocation.proceed方法!如果不调用,后果很严重!!!

比如我们实现事务超时的mybatis拦截器如下

    @Intercepts({
            @Signature(type = StatementHandler.class, method = "update", args = {Statement.class}),
            @Signature(type = StatementHandler.class, method = "batch", args = {Statement.class}),
            @Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class})})
    public class MybatisTransactionTimeoutInterceptor implements Interceptor {
     
        @Override
        public Object intercept(Invocation invocation) throws Throwable {
            Statement stmt = (Statement) invocation.getArgs()[0];
            Collection<Object> values = TransactionSynchronizationManager.getResourceMap().values();
            if (!values.isEmpty()) {
                for (Object obj : values) {
                    if (obj != null && obj instanceof ConnectionHolder) {
                        ConnectionHolder holder = (ConnectionHolder) obj;
                        if (holder.hasTimeout()) {
                            int queryTimeOut = holder.getTimeToLiveInSeconds();
                            if (stmt.getQueryTimeout() != 0) {
                                queryTimeOut = queryTimeOut < stmt.getQueryTimeout() ? queryTimeOut : stmt.getQueryTimeout();
                            }
                            stmt.setQueryTimeout(queryTimeOut);
                        }
                        break;
                    }
                }
            }
     
            return invocation.proceed();
        }
     
        @Override
        public Object plugin(Object target) {
            if (target instanceof StatementHandler) {
                return Plugin.wrap(target, this);
            } else {
                return target;
            }
        }
     
        @Override
        public void setProperties(Properties properties) {
            // Do nothing
        }
    }
复制代码

正如我们所说 事实上interceptor的签名匹配思路 因此我们可以对于mybatis插件做如下优化

    @Override
       public Object plugin(Object target) {
           if (target instanceof StatementHandler) {
               return Plugin.wrap(target, this);
           } else {
               return target;
           }
       }
复制代码

这样可以有效的降低mybatis的代理链嵌套层次 特别是插件太多的情况!

那么我们什么情况才有机会生成动态的插件呢?

Mybatis在启动时会调用InterceptorChain做对应的初始化

    public class InterceptorChain {
     
      private final List<Interceptor> interceptors = new ArrayList<Interceptor>();
     
      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);
      }
     
    }
复制代码

其中有四个地方调用

从上述可知 因此mybatis的插件支持如上四种

  1. parameterHandler
  2. resultSetHandler
  3. statementHandler
  4. executor
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值