• Mybaties插件原理


    本文主要解析Mybaties插件基本原理,所谓的插件就是Mybaties提供的Interceptor拦截器,用于SQL执行时动态对“执行方法”、“参数”、“返回值”或“SQL语句”的拦截处理。在业务很多场景都会用到,如分页和sql操作同步等场景。

    1. MyBatis拦截原理

    MyBatis插件可以拦截四个接口的方法:ExecutorParameterHandlerStatementHandlerResultSetHandler。顾名思义,这四个接口都有名称相近的处理操作,前述四个接口分别表示:执行器拦截、参数拦截、结果集处理和SQL构建。以Executor为例,拦截Executorint update(MappedStatement ms, Object parameter) throws SQLException方法,也就是业务在执行insert、update或者delete操作前后都会被该方法拦截。将操作拦截下来,判断结果是否正确或者满足,在自定义操作。

    拦截器接口代码

    package org.apache.ibatis.plugin;
    
    import java.util.Properties;
    
    /**
     * @author Clinton Begin
     */
    public interface Interceptor {
      
      // 拦截处理放在该方法中
      Object intercept(Invocation invocation) throws Throwable;
    
      // 代理生成实现类对象,说白了为了执行实现类对象的intercept方法
      Object plugin(Object target);
    
      void setProperties(Properties properties);
    }
    

    Interceptor.plugin(Object target)方法被InterceptorChain.pluginAll(Object target)方法调用,源码

    package org.apache.ibatis.plugin;
    
    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.List;
    
    /**
     * @author Clinton Begin
     */
    public class InterceptorChain {
    
      private final List<Interceptor> interceptors = new ArrayList<Interceptor>();
    
      // 会被Configuration配置类调用
      // 在进行增删改查时,会遍历所有拦截器,并执行其plugin方法
      public Object pluginAll(Object target) {
        for (Interceptor interceptor : interceptors) {
          target = interceptor.plugin(target);
        }
        return target;
      }
    
      // 会被Configuration.addInterceptor(Interceptor interceptor)调用
      public void addInterceptor(Interceptor interceptor) {
        interceptors.add(interceptor);
      }
      
      public List<Interceptor> getInterceptors() {
        return Collections.unmodifiableList(interceptors);
      }
    }
    

    MyBatis提供了@Intercepts@Signature拦截器相关的注解。增删改查等数据库操作会进入拦截器Interceptor,拦截时会根据拦截器上的这两个注解值来确定拦截与否。比如@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})})表示该拦截器将处理Executor组件且参数是(MappedStatement.class, Object.class)的update方法。具体的判断逻辑是在Plugin.wrap(target, this)中。

    Mybaties的Plugin类源码

    // 该类实现了InvocationHandler接口,用于JDK动态代理
    // 这样做的好处是在动态代理调用invoke方法时,会直接使用生成代理对象时缓存的方法如target、interceptor和signatureMap。
    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) {
        // @Signature注解的type属性为key,method属性集合为value的Map对象
        Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
        Class<?> type = target.getClass();
        // 获取被代理对象实现的接口,但接口类型必须在signatureMap的key中
        Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
        // JDK动态代理是接口的代理,实现接口不能为空
        if (interfaces.length > 0) {
          // 生成代理对象
          return Proxy.newProxyInstance(
              type.getClassLoader(),
              interfaces,
              new Plugin(target, interceptor, signatureMap));
        }
        return target;
      }
    
      // 代理Interceptor.intercept(Invocation invocation)方法
      @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);
        }
      }
    
      // 该方法主要用于返回参数是@Signature注解的type属性为key,method属性集合为value的Map对象
      private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
        // 拿到拦截器上Intercepts注解,不存在时会抛异常
        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) {
          // sig.type()即是@Signature注解的type属性
          Set<Method> methods = signatureMap.get(sig.type());
          // signatureMap没有值时,新建一个Set存入
          if (methods == null) {
            methods = new HashSet<Method>();
            signatureMap.put(sig.type(), methods);
          }
          try {
            // 通过函数名和参数类型确定具体的Method
            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;
      }
    
      // 获取被代理对象实现的接口,但接口类型必须在signatureMap的key中
      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()) {
            // 要包含在signatureMap的key中
            if (signatureMap.containsKey(c)) {
              interfaces.add(c);
            }
          }
          // 父类
          type = type.getSuperclass();
        }
        return interfaces.toArray(new Class<?>[interfaces.size()]);
      }
    }
    

    在调用Mybaties配置类Configuration的newExecutornewParameterHandlernewResultSetHandlernewStatementHandler方法时都会调用所有拦截器。

    2. 一个拦截器示例

    package com.mingo.es.sync.mybaties.plugin;
    
    import lombok.extern.slf4j.Slf4j;
    import org.apache.ibatis.executor.Executor;
    import org.apache.ibatis.executor.statement.StatementHandler;
    import org.apache.ibatis.mapping.MappedStatement;
    import org.apache.ibatis.plugin.*;
    import org.springframework.stereotype.Component;
    
    import java.sql.Statement;
    import java.util.Arrays;
    import java.util.Properties;
    
    /**
     * MyBaties拦截测试。这里只拦截处理:
     * Executor.update(MappedStatement ms, Object parameter) 和
     * StatementHandler.update(Statement statement)
     *
     * update表示insert、update和delete
     * 
     * @author Doflamingo
     */
    @Component
    @Slf4j
    @Intercepts({
            @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
            @Signature(type = StatementHandler.class, method = "update", args = {Statement.class}),
    })
    public class TestInterceptor implements Interceptor {
    
        /**
         * 拦截处理
         *
         * @param invocation
         * @return
         * @throws Throwable
         */
        @Override
        public Object intercept(Invocation invocation) throws Throwable {
            Object res = invocation.proceed();
    
            System.out.println("拦截处理的接口和方法分别是:" +
                    Arrays.toString(invocation.getTarget().getClass().getInterfaces()) + ", " +
                    invocation.getMethod().getName()
            );
            return res;
        }
    
        /**
         * 生成代理对象
         *
         * @param target
         * @return
         */
        @Override
        public Object plugin(Object target) {
            // 代理对象,用于触发intercept()
            return Plugin.wrap(target, this);
        }
    
        @Override
        public void setProperties(Properties properties) {
        }
    }
    

    StatementHandler.update(Statement statement)方法表示将 insert、update、delete 操作推送到数据库。写一个测试用例,将一条数据插入数据库表中。执行结果如下

    拦截处理的接口和方法分别是:[interface org.apache.ibatis.executor.statement.StatementHandler], update
    拦截处理的接口和方法分别是:[interface org.apache.ibatis.executor.Executor], update
    
    原创 Doflamingo https://www.cnblogs.com/doflamingo
  • 相关阅读:
    通用页面调用APP 互通
    HTML5[5]:在移动端禁用长按选中文本功能
    JAVA 中的基础
    手机访问PC网站自动跳转到手机网站代码
    自适应的设置字体的方式
    localStorage 与 sessionStorage
    《高级程序设计》3 基本慨念
    javascript基础
    jQuery技巧
    jQuery性能优化
  • 原文地址:https://www.cnblogs.com/doflamingo/p/13697419.html
Copyright © 2020-2023  润新知