Mybatis 插件

作者: 93张先生 | 来源:发表于2020-09-09 16:04 被阅读0次

    插件

    插件是一种常见的扩展方式,来扩展或改变框架的原有功能。
    Mybatis 插件实际是一种拦截器(Interceptor),使用 JDK 代理动态代理来实现,多个插件,就又设计到了责任链模式。

    责任链模式

    在责任链模式中,将上述完整的、脚肿的接收者的实现逻辑拆分到多个只包含部分逻辑的、功能单一的 Handler 处理类中,开发人员可以根据业务需求将多个 Handler 对象组合成 条责任链,实现请求的处理。在一条责任链中,每个 Handler 对象都包含对下一个 Handler 对象的引用,一个 Handler 对象处理完成请求消息(或者不能处理该请求),会把请求传给下一个 Handler 对象继续处理,以此类推,直至整条责任链结束。

    责任链类图
    image.png

    Interceptor

    Mybatis 允许用户使用自定义拦截器对 SQL 语句执行过程中的某一点进行拦截。默认情况下, MyBatis 允许拦截器拦截 Executor 方法、 ParameterHandler 方法、 ResultSetHandler方法以及 StatementHandler 的方法。 具体可拦截方法如下

    • Executor.update()方法、 query()方法、 flushStatements()方法、 commit()方法、 rollback()方法、 getTransaction()方法、 close()方法、 isClosed()方法。
    • ParameterHandler 中的 etParameterObject()方法、 setParameters()方法。
    • ResultSetHandler 中的 handleReultSets()方法、 handleOutputParameters()方法。
    • StatementHandler 中的 prepare()方法、 parameterize()方法、 batch()方法、 update()方法、query()方法。

    Mybtis 中使用的拦截器都需要实现 Interceptor 接口。 Interceptor 接口是 Mybtis 插件模块核心。

    插件实现步骤

    1.用户自定义拦截器,需要实现 Interceptor 接口,并在类上使用 @Interceptors 注解,然后具体拦截的方法使用 @Signature 注解

     @Interceptors({
       @Signature(type = Executor.class,method = "query",args = [MappedStatement.class,Object.class,RowBounds.class,ResultHandler.class]),
       @Signature(type = Executor.class,method = "close",args = [boolean.class])
     })
    

    2.然后在 mybatis-config.xml 中对拦截器进行配置

     <plugins>
       <plugin interceptor = "com.test.ExamplePlugin">
         <property name="testProp" value="100"/>
       </plugin>
     </plugins>
    

    3.在 Mybatis 初始化时,会通过 XMLConfigBuilder.pluginElement() 方法解析 mybatis-config.xml 配置文件中定义的<plugins> 节点,调用 Interceptor.setProperties() 初始化属性,然后添加到 Configuration.interceptorChain 插件链中。

     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 的具体实现
             Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();
             // 设置插件的属性
             interceptorInstance.setProperties(properties);
             // 设置插件到拦截器链中
             configuration.addInterceptor(interceptorInstance);
           }
         }
       }
    
    1. MyBatis 对 Executor、ParameterHandler、ResultSetHandler、StatementHandler 进行拦截。这四个对象都是 Configuration.new*() 系列创建的。
      其中会通过 interceptorChain.pluginAll(target) 为目标添加拦截器,然后返回被拦截对象 target 对象的代理。
      public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
         executorType = executorType == null ? defaultExecutorType : executorType;
         executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
         Executor executor;
         if (ExecutorType.BATCH == executorType) {
           executor = new BatchExecutor(this, transaction);
         } else if (ExecutorType.REUSE == executorType) {
           executor = new ReuseExecutor(this, transaction);
         } else {
           executor = new SimpleExecutor(this, transaction);
         }
         // 是否开启二级缓存,装饰器模式
         if (cacheEnabled) {
           executor = new CachingExecutor(executor);
         }
         // 通过interceptorChain.pluginAll() 方法创建 Executor 的代理对象
         executor = (Executor) interceptorChain.pluginAll(executor);
         return executor;
       }
    

    5.interceptorChain.pluginAll() 调用 interceptor.plugin(target) 方法返回被拦截对象的代理对象。
    6.被代理对象被调用,如果不是拦截方法,则反射进行被拦截对象的方法调用;如果是拦截对象,调用拦截方法,然后调用被拦截对象的真实方法。

    Interceptor

    定义插件,需要实现的拦截器接口

    public interface Interceptor {
      // 执行拦截逻辑的方法
      Object intercept(Invocation invocation) throws Throwable;
      // 为被拦截对象,装备拦截器,并返回代理对象
      default Object plugin(Object target) {
        return Plugin.wrap(target, this);
      }
      // 根据配置初始化 Interceptor 对象
      default void setProperties(Properties properties) {
        // NOP No Operation Performed
      }
    
    }
    
    Plugin

    插件的实现,把拦截器的功能包装进项目里插件容器

    public class Plugin implements InvocationHandler {
      // 被拦截对象
      private final Object target;
      // 拦截器
      private final Interceptor interceptor;
      // 拦截器 @Signature 注解中的信息
      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;
      }
    
      /**
       * 为被拦截对象,装配拦截器,返回创建的代理对象
       * @param target 被拦截对象
       * @param interceptor 拦截器
       * @return
       */
      public static Object wrap(Object target, Interceptor interceptor) {
        // 获取用户自定义 Interceptor 中 @Signature 注解的信息
        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;
      }
    
      /**
       * 通过 invoke 方法,调用被代理对象的方法
       * @param proxy
       * @param method
       * @param args
       * @return
       * @throws Throwable
       */
      @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
          // 获取当前方法所在类或接口中,可被当前 Interceptor 拦截的方法
          Set<Method> methods = signatureMap.get(method.getDeclaringClass());
          // 如采当前调用的方法需妥被拦截,则调用 Interceptor.interceptor() 方法进行拦截处理
          if (methods != null && methods.contains(method)) {
            return interceptor.intercept(new Invocation(target, method, args));
          }
          // 如采当前调用的方法不能被拦截,则调用 target 对象的相应方法
          return method.invoke(target, args);
        } catch (Exception e) {
          throw ExceptionUtil.unwrapThrowable(e);
        }
      }
    
      /**
       * 处理 @Signature 注解的信息
       * @param interceptor
       * @return
       */
      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<>();
        for (Signature sig : sigs) {
          Set<Method> methods = signatureMap.computeIfAbsent(sig.type(), k -> new HashSet<>());
          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;
      }
    
      /**
       * 获取目标类型实现的接口,Executor 等被拦截对象的具体实现类
       * @param type
       * @param signatureMap
       * @return
       */
      private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
        Set<Class<?>> interfaces = new HashSet<>();
        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()]);
      }
    
    }
    
    
    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;
      }
    
      /**
       * 拦截器已经被调用,继续调用被拦截对象的真实需要的方法
       * @return
       * @throws InvocationTargetException
       * @throws IllegalAccessException
       */
      public Object proceed() throws InvocationTargetException, IllegalAccessException {
        return method.invoke(target, args);
      }
    
    }
    

    场景

    • MyBatis 的分页插件
    • MyBatis 生成统一主键
    • MyBatis 的分库分表实现

    相关文章

      网友评论

        本文标题:Mybatis 插件

        本文链接:https://www.haomeiwen.com/subject/fycjektx.html