[JAVA] MyBatis插件机制超详细讲解

1954 0
黑夜隐士 2022-11-8 16:57:22 | 显示全部楼层 |阅读模式
目录

    MyBatis的插件机制
      InterceptorChainMyBatis中的Plugin
    MyBatis插件开发总结


MyBatis的插件机制

MyBatis 允许在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
    Executor(update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)ParameterHandler(getParameterObject, setParameters)ResultSetHandler(handleResultSets, handleOutputParameters)StatementHandler(prepare, parameterize, batch, update, query)
这里我们再回顾一下,在创建StatementHandler时,我们看到了如下代码:
  1. public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
  2.    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
  3.    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
  4.    return statementHandler;
  5. }
复制代码
InterceptorChain

在全局配置Configuration中维护了一个InterceptorChain interceptorChain = new InterceptorChain()拦截器链,其内部维护了一个私有常量List<Interceptor> interceptors,其pluginAll方法为遍历interceptors并调用每个拦截器的plugin方法。
  1. public class InterceptorChain {
  2.   private final List<Interceptor> interceptors = new ArrayList<Interceptor>();
  3.   public Object pluginAll(Object target) {
  4.     for (Interceptor interceptor : interceptors) {
  5.       target = interceptor.plugin(target);
  6.     }
  7.     return target;
  8.   }
  9. //添加拦截器到链表中
  10.   public void addInterceptor(Interceptor interceptor) {
  11.     interceptors.add(interceptor);
  12.   }
  13.   //获取链表中的拦截器,返回一个不可修改的列表
  14.   public List<Interceptor> getInterceptors() {
  15.     return Collections.unmodifiableList(interceptors);
  16.   }
  17. }
复制代码
MyBatis中拦截器接口
  1. package org.apache.ibatis.plugin;
  2. import java.util.Properties;
  3. public interface Interceptor {
  4.   //拦截处理,也就是代理对象目标方法执行前被处理       
  5.   Object intercept(Invocation invocation) throws Throwable;
  6.   //生成代理对象
  7.   Object plugin(Object target);
  8.   //设置属性
  9.   void setProperties(Properties properties);
  10. }
复制代码
如果想自定义插件,那么就需要实现该接口。
MyBatis中的Invocation
  1. package org.apache.ibatis.plugin;
  2. import java.lang.reflect.InvocationTargetException;
  3. import java.lang.reflect.Method;
  4. public class Invocation {
  5. //目标对象
  6. private final Object target;
  7. //目标对象的方法
  8. private final Method method;
  9. //方法参数
  10. private final Object[] args;
  11. public Invocation(Object target, Method method, Object[] args) {
  12.    this.target = target;
  13.    this.method = method;
  14.    this.args = args;
  15. }
  16. public Object getTarget() { return target;}
  17. public Method getMethod() {return method;}
  18. public Object[] getArgs() {return args;}
  19. //proceed-继续,也就是说流程继续往下执行,这里看方法就是目标方法反射调用。
  20. public Object proceed() throws InvocationTargetException, IllegalAccessException {
  21.    return method.invoke(target, args);
  22. }
  23. }
复制代码
MyBatis中的Plugin
  1. package org.apache.ibatis.plugin;
  2. import java.lang.reflect.InvocationHandler;
  3. import java.lang.reflect.Method;
  4. import java.lang.reflect.Proxy;
  5. import java.util.HashMap;
  6. import java.util.HashSet;
  7. import java.util.Map;
  8. import java.util.Set;
  9. import org.apache.ibatis.reflection.ExceptionUtil;
  10. public class Plugin implements InvocationHandler {
  11.   //目标对象 ,被代理的对象
  12.   private final Object target;
  13.   //拦截器
  14.   private final Interceptor interceptor;
  15.   //方法签名集合
  16.   private final Map<Class<?>, Set<Method>> signatureMap;
  17.   private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
  18.     this.target = target;
  19.     this.interceptor = interceptor;
  20.     this.signatureMap = signatureMap;
  21.   }
  22.   //该方法会获取signatureMap中包含的所有target实现的接口,然后生成代理对象
  23.   public static Object wrap(Object target, Interceptor interceptor) {
  24.     Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
  25.     Class<?> type = target.getClass();
  26.     Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
  27.     if (interfaces.length > 0) {
  28.       return Proxy.newProxyInstance(
  29.           type.getClassLoader(),
  30.           interfaces,
  31.           new Plugin(target, interceptor, signatureMap));
  32.     }
  33.     return target;
  34.   }
  35.   //每一个InvocationHandler 的invoke方法会在代理对象的目标方法执行前被触发
  36.   @Override
  37.   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  38.     try {
  39.       Set<Method> methods = signatureMap.get(method.getDeclaringClass());
  40.       if (methods != null && methods.contains(method)) {
  41.         return interceptor.intercept(new Invocation(target, method, args));
  42.       }
  43.       return method.invoke(target, args);
  44.     } catch (Exception e) {
  45.       throw ExceptionUtil.unwrapThrowable(e);
  46.     }
  47.   }
  48.         //获取拦截器感兴趣的接口与方法
  49.   private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
  50.     Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
  51.     // issue #251
  52.     if (interceptsAnnotation == null) {
  53.       throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
  54.     }
  55.     Signature[] sigs = interceptsAnnotation.value();
  56.     Map<Class<?>, Set<Method>> signatureMap = new HashMap<>();
  57.     for (Signature sig : sigs) {
  58.       Set<Method> methods = signatureMap.computeIfAbsent(sig.type(), k -> new HashSet<>());
  59.       try {
  60.         Method method = sig.type().getMethod(sig.method(), sig.args());
  61.         methods.add(method);
  62.       } catch (NoSuchMethodException e) {
  63.         throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
  64.       }
  65.     }
  66.     return signatureMap;
  67.   }
  68.         //该方法会获取signatureMap中包含的所有type实现的接口与上级接口
  69.   private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
  70.     Set<Class<?>> interfaces = new HashSet<>();
  71.     while (type != null) {
  72.       for (Class<?> c : type.getInterfaces()) {
  73.         if (signatureMap.containsKey(c)) {
  74.           interfaces.add(c);
  75.         }
  76.       }
  77.       type = type.getSuperclass();
  78.     }
  79.     return interfaces.toArray(new Class<?>[interfaces.size()]);
  80.   }
  81. }
复制代码
这里Plugin实现了InvocationHandler,那么其invoke方法会在代理对象的目标方法执行前被触发。其invoke方法解释如下:
    ① 获取当前Plugin感兴趣的方法类型,判断目标方法Method是否被包含;② 如果当前目标方法是Plugin感兴趣的,那么就interceptor.intercept(new Invocation(target, method, args));触发拦截器的intercept方法;③ 如果当前目标方法不是Plugin感兴趣的,直接执行目标方法。
上面说Plugin感兴趣其实是指内部的interceptor感兴趣。

MyBatis插件开发

如下所示,编写插件实现Interceptor接口,并使用@Intercepts注解完成插件签名。
  1. package com.mybatis.dao;
  2. import java.util.Properties;
  3. import org.apache.ibatis.executor.parameter.ParameterHandler;
  4. import org.apache.ibatis.executor.statement.StatementHandler;
  5. import org.apache.ibatis.plugin.Interceptor;
  6. import org.apache.ibatis.plugin.Intercepts;
  7. import org.apache.ibatis.plugin.Invocation;
  8. import org.apache.ibatis.plugin.Plugin;
  9. import org.apache.ibatis.plugin.Signature;
  10. import org.apache.ibatis.reflection.MetaObject;
  11. import org.apache.ibatis.reflection.SystemMetaObject;
  12. /**
  13. * 完成插件签名:告诉MyBatis当前插件用来拦截哪个对象的哪个方法
  14. */
  15. @Intercepts({@Signature(type=StatementHandler.class,method="parameterize",args=java.sql.Statement.class)})
  16. public class MyFirstPlugin implements Interceptor{
  17.         @Override
  18.         public Object intercept(Invocation invocation) throws Throwable {
  19.                 // TODO Auto-generated method stub
  20.                 System.out.println("MyFirstPlugin...intercept:"+invocation.getMethod());
  21.                 Object target = invocation.getTarget();
  22.                 System.out.println("当前拦截到的对象:"+target);
  23.                 //拿到:StatementHandler==>ParameterHandler===>parameterObject
  24.                 //拿到target的元数据
  25.                 MetaObject metaObject = SystemMetaObject.forObject(target);
  26.                 Object value = metaObject.getValue("parameterHandler.parameterObject");
  27.                 System.out.println("sql语句用的参数是:"+value);
  28.                 //修改完sql语句要用的参数
  29.                 metaObject.setValue("parameterHandler.parameterObject", 11);
  30.                 //执行目标方法
  31.                 Object proceed = invocation.proceed();
  32.                 //返回执行后的返回值
  33.                 return proceed;
  34.         }
  35.          //plugin:包装目标对象的:包装:为目标对象创建一个代理对象
  36.         @Override
  37.         public Object plugin(Object target) {
  38.                 //我们可以借助Plugin的wrap方法来使用当前Interceptor包装我们目标对象
  39.                 System.out.println("MyFirstPlugin...plugin:mybatis将要包装的对象"+target);
  40.                 Object wrap = Plugin.wrap(target, this);
  41.                 //返回为当前target创建的动态代理
  42.                 return wrap;
  43.         }
  44.          //setProperties:将插件注册时 的property属性设置进来
  45.         @Override
  46.         public void setProperties(Properties properties) {
  47.                 // TODO Auto-generated method stub
  48.                 System.out.println("插件配置的信息:"+properties);
  49.         }
  50. }
复制代码
注册到mybatis的全局配置文件中,示例如下(注意,插件是可以设置属性的如这里我们可以设置用户名、密码):
<plugins>
    <plugin interceptor="com.mybatis.dao.MyFirstPlugin">
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </plugin>
</plugins>
那么mybatis在执行过程中实例化Executor、ParameterHandler、ResultSetHandler和StatementHandler时都会触发上面我们自定义插件的plugin方法。
如果有多个插件,那么拦截器链包装的时候会从前到后,执行的时候会从后到前。如这里生成的StatementHandler代理对象如下:



总结

    按照插件注解声明,按照插件配置顺序调用插件plugin方法,生成被拦截对象的动态代理;多个插件依次生成目标对象的代理对象,层层包裹,先声明的先包裹,形成代理链;目标方法执行时依次从外到内执行插件的intercept方法。多个插件情况下,我们往往需要在某个插件中分离出目标对象。可以借助MyBatis提供的SystemMetaObject类来进行获取最后一层的h以及target属性的值
到此这篇关于MyBatis插件机制超详细讲解的文章就介绍到这了,更多相关MyBatis插件机制内容请搜索中国红客联盟以前的文章或继续浏览下面的相关文章希望大家以后多多支持中国红客联盟!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

中国红客联盟公众号

联系站长QQ:5520533

admin@chnhonker.com
Copyright © 2001-2025 Discuz Team. Powered by Discuz! X3.5 ( 粤ICP备13060014号 )|天天打卡 本站已运行