MyBatis插件小知识点


声明:本文转载自https://my.oschina.net/qixiaobo025/blog/1588352,转载目的在于传递更多信息,仅供学习交流之用。如有侵权行为,请联系我,我会及时删除。

背景

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

本文发表于2017年12月12日 12:33
(c)注:本文转载自https://my.oschina.net/qixiaobo025/blog/1588352,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。如有侵权行为,请联系我们,我们会及时删除.

阅读 1694 讨论 0 喜欢 0

抢先体验

扫码体验
趣味小程序
文字表情生成器

闪念胶囊

你要过得好哇,这样我才能恨你啊,你要是过得不好,我都不知道该恨你还是拥抱你啊。

直抵黄龙府,与诸君痛饮尔。

那时陪伴我的人啊,你们如今在何方。

不出意外的话,我们再也不会见了,祝你前程似锦。

这世界真好,吃野东西也要留出这条命来看看

快捷链接
网站地图
提交友链
Copyright © 2016 - 2021 Cion.
All Rights Reserved.
京ICP备2021004668号-1