• Mybatis插件机制


    什么是插件机制

    插件插件, 就是能在执行某个方法之前加入一些功能代码, 有啥方法能够实现呢?当然是动态代理了, 为啥要使用动态代理应为他是为了写框架扩展性必备的东西。 只要定义一些接口 或者类 就行使用jdk自带的或者CGLIB之类的动态代理库完成方法的织入。

    学习之前需要掌握的知识点

    1、 动态代理 2、注解 3、反射 4、责任链的设计模式

    反射调用对象

    public class Invocation {
    
      private final Object target; //目标对象
      private final Method method; //目标方法
      private final Object[] args; //目标参数
    
      public Object proceed() throws InvocationTargetException, IllegalAccessException {
        return method.invoke(target, args); //反射调用目标方法
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    这里需要注意 proceed这个方法, 因为这个方法的设计得很重要。

    Interceptor 接口设计

    public interface Interceptor {
      /**
       * 拦截执行方法
       * @param invocation
       * @return
       * @throws Throwable
       */
      Object intercept(Invocation invocation) throws Throwable;
    
      default Object plugin(Object target) {
        return Plugin.wrap(target, this); //对目标对象进行动态代理,this为拦截器
      }
    
      default void setProperties(Properties properties) {/*properties 属性注射方法*/
        // NOP
      }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    责任链模式的设计

    public class InterceptorChain {
    
      private final List<Interceptor> interceptors = new ArrayList<>(); //所有的执行器链
    
      public Object pluginAll(Object target) {//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);
      }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    注解设计

    我们插件机制最细维度是希望能够为某个具体的方法进行功能上的增强。在java语法中, 一个方法的标识 需要有所属的Class对象,已经方法名称 , 已经方法形参类型。 所以我们可以设计一个注解来说明要在那个类对象的那个方法进行插件机制的功能增强。

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target({})
    public @interface Signature {
      /**
       * Returns the java type.
       *
       * @return the java type
       */
      Class<?> type(); //类对象
    
      /**
       * Returns the method name.
       *
       * @return the method name
       */
      String method();//方法
    
      /**
       * Returns java types for method argument.
       * @return java types for method argument
       */
      Class<?>[] args(); //参数的类对象数组
    }
    
    
    • 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

    当然为了支持一个拦截器能够插入多个方法, 我们需要再设计一个注解 也就是支持 @Signature 注解的数组形式。

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    public @interface Intercepts {
      /**
       * Returns method signatures to intercept.
       *
       * @return method signatures
       */
      Signature[] value(); //数组形式
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    解析注解

    注解提供了配置说明, 但是具体这个配置说明的功能还需要我们去编写代码去实现。

    提取注解 getSignatureMap

    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(); //获取注解的value
        Map<Class<?>, Set<Method>> signatureMap = new HashMap<>(); //创建存储对象
        for (Signature sig : sigs) { //遍历数组
          Set<Method> methods = MapUtil.computeIfAbsent(signatureMap, sig.type(), k -> new HashSet<>()); //判断这个类对象的Set是否存在,不存在就新建,存在就用原来的
          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

    这样我们就获得了 该拦截器插件 能对那个类的那些方法进行增强的信息。那么接下来就是需要判断增强的目标对象是否在插件的类和方法声明内。

    /**
       * 
       * @param type 增强的目标方法
       * @param signatureMap 增强允许的类和方法列表
       * @return 增强的接口
       */
      private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
        Set<Class<?>> interfaces = new HashSet<>(); //用来存放实现的接口, 因为采用的是jdk的动态代理
        while (type != null) {
          for (Class<?> c : type.getInterfaces()) { //获取当前类的所有接口
            if (signatureMap.containsKey(c)) { //接口是否在提取出来的注解信息中存在
              interfaces.add(c);
            }
          }
          type = type.getSuperclass(); //获取父类
        }
        return interfaces.toArray(new Class<?>[0]);
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    进行代理

    public static Object wrap(Object target, Interceptor interceptor) {
        Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor); //提取Intercepts注解信息完毕
        Class<?> type = target.getClass(); //获取目标类类对象
        Class<?>[] interfaces = getAllInterfaces(type, signatureMap); //获取type 在signatrueMap中所有的接口集合
        if (interfaces.length > 0) {//说明有接口, 进行动态代理
          return Proxy.newProxyInstance(
              type.getClassLoader(), //当前类的类加载器
              interfaces, //接口
              new Plugin(target, interceptor, signatureMap)); //invocationhandler
        }
        return target;
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    Invocationhandler 精髓实现

     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

    源码插件机制扩展点

    从以上代码分析,要实现代理我们只需要调用InterceptorChain.pluginAll 传入目标方法就行。 Mybatis源码中留下这样的扩展点有那些呢?

    其实只要看pluginAll在那些源码中用到就行, 在IDEA中只需在鼠标方法在这个方法上然后按住ctrl 接着再按鼠标左键
    在这里插入图片描述

    可见Mybatis源码中一共留下了4个插件扩展点, 分别是对参数处理器 、 结果集处理器、表达式处理器、执行器处理器的增强代理。

  • 相关阅读:
    卷积神经网络模型之——VGG-16网络结构与代码实现
    java计算机毕业设计Web商铺租赁管理系统MyBatis+系统+LW文档+源码+调试部署
    IOS手机和车机互联自动化测试
    用二叉树或栈求表达式的值--代码实现+算法分析
    什么样的台灯适合学生使用?五款暑假必入护眼大路灯分享
    SwiftUI 4.0为Toggle视图在iOS 16中增加属性多选功能
    提出问题,解答问题!这才是理解代码设计的正确方法
    Postman接口测试之Mock快速入门
    linux下搭建gperftools工具分析程序瓶颈
    前后端分离 RSA 加密登录流程(记录)
  • 原文地址:https://blog.csdn.net/nobaldnolove/article/details/128072752