• Mybatis Plugin插件


    Plugin

    首先mybatisPlugin是针对于mybatis四大组件(Statementhandler、resultsethandler、parameterHandler、executor)做增强操作的。
    MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
    Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
    ParameterHandler (getParameterObject, setParameters)
    ResultSetHandler (handleResultSets, handleOutputParameters)
    StatementHandler (prepare, parameterize, batch, update, query)
    通过 MyBatis 提供的强大机制,使用插件是非常简单的,只需实现 Interceptor 接口,并指定想要拦截的方法签名即可。
    以pageHlper拦截器为例

    @SuppressWarnings("rawtypes")
    @Intercepts(@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}))
    public class PageHelper implements Interceptor
    
    
    • 1
    • 2
    • 3
    • 4

    根据注解构建signatureMap

    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;
      }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    key为自定义拦截器接口 ,参数时是方法参数
    多个拦截器就会代理多次,对代理类再进行代理。

    public interface Interceptor {
    //执行目标方法
    Object intercept(Invocation invocation) throws Throwable;
    //执行pluginAll调用
    Object plugin(Object target);
    //xml配置的初始化参数值
    void setProperties(Properties properties);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    我们要实现逻辑的方法是intercept
    对于plugin方法,可以自定义一些场景

     @Override
        public Object plugin(Object target) {
            if (target instanceof Executor) {
                return Plugin.wrap(target, this);
            }
            return target;
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    例子

    @Intercepts({@Signature(type = Executor.class, method = "query",
            args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})
    
    • 1
    • 2
      public Object intercept(Invocation invocation) throws Throwable {
            Object[] args = invocation.getArgs();
            MappedStatement ms = (MappedStatement) args[0];
            Object parameterObject = null;
            try {
    
                 parameterObject = args[1];
            }catch (Exception e){
                System.out.println(e);
            }
            // id为执行的mapper方法的全路径名,如com.mapper.UserMapper
            String id = ms.getId();
            String sqlCommandType = ms.getSqlCommandType().toString();
            if (!sqlCommandType.equals(SqlCommandType.SELECT.toString())) {
                return invocation;
            }
            BoundSql boundSql = ms.getBoundSql(parameterObject);
            String origSql = boundSql.getSql();
            String newSql = getNewSql(id, origSql);
            if (newSql.equals(origSql)) {
                return invocation;
            }
            // 重新new一个查询语句对象
            BoundSql newBoundSql = new BoundSql(ms.getConfiguration(), newSql,
                    boundSql.getParameterMappings(), boundSql.getParameterObject());
    
            // 把新的查询放到statement里
            MappedStatement newMs = newMappedStatement(ms, new BoundSqlSqlSource(newBoundSql));
            for (ParameterMapping mapping : boundSql.getParameterMappings()) {
                String prop = mapping.getProperty();
                if (boundSql.hasAdditionalParameter(prop)) {
                    newBoundSql.setAdditionalParameter(prop, boundSql.getAdditionalParameter(prop));
                }
            }
    
            Object[] queryArgs = invocation.getArgs();
            queryArgs[0] = newMs;
            logger.info("由于数据权限,SQL被改写 originSql: {},newSql: {}", origSql, newSql);
            return invocation;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    获取查询sql并改写

    原理分析

    再创建四大核心对象时会对对象做代理操作
    例如
    ![image.png](https://img-blog.csdnimg.cn/img_convert/52d370b36badb80659bc7efe791f144e.png#clientId=u30d5b9a5-e98a-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=515&id=ub7d803a1&margin=[object Object]&name=image.png&originHeight=772&originWidth=1181&originalType=binary&ratio=1&rotation=0&showTitle=false&size=93254&status=done&style=none&taskId=udbc4c837-4a55-4a70-8932-6d854c219a4&title=&width=787.3333333333334)
    代理类Plugin

    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;
      }
    
    
      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;
      }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    wrap方法会根据注解信息选择性的进行代理操作

    package org.apache.ibatis.plugin;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.util.HashMap;
    import java.util.HashSet;
    import java.util.Map;
    import java.util.Set;
    
    import org.apache.ibatis.reflection.ExceptionUtil;
    
    /**
     * @author Clinton Begin
     */
    public class Plugin implements InvocationHandler {
    
    
      @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);
        }
      }
    
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    内部逻辑就是获取签名信息,执行时。匹配目标方法和注解中的方法参数,如果匹配上,那么执行拦截器interceptor。

  • 相关阅读:
    水泥设备如何实现物联网远程监控?
    设计模式——组合模式(Composite Pattern)+ Spring相关源码
    剑指 Offer 53 - II. 0~n-1中缺失的数字
    通过配置文件修改docker容器端口映射
    github.com/yuin/gopher-lua 踩坑日记
    cloud探索 - AWS容器
    基于Python+tkinter实现一个简易计算器桌面软件
    day10-Tomcat02
    【Java】Java基础习题1
    YashanDB个人版正式开放下载!参与首批体验官活动赢好礼!
  • 原文地址:https://blog.csdn.net/qq_37436172/article/details/127623784