• Mybatis3源码笔记(七)Plugin


    1.Mybatis3的插件其实主要是用到了责任链和动态代理两种模式相结合而生成的。下面我们看一个例子,在执行所有update操作时,执行一个小小的测试输出。

    @Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})})
    public class ExamplePlugin implements Interceptor {
      private Properties properties;
    
      @Override
      public Object intercept(Invocation invocation) throws Throwable {
        //do something
        System.out.println(properties.getProperty("pluginProperty"));
        return invocation.proceed();
      }
    
      @Override
      public Object plugin(Object target) {
        return Plugin.wrap(target, this);
      }
    
      @Override
      public void setProperties(Properties properties) {
        this.properties = properties;
      }
    
      public Properties getProperties() {
        return properties;
      }
    
    }
    
      <plugins>
        <plugin interceptor="org.apache.ibatis.builder.ExamplePlugin">
          <property name="pluginProperty" value="hello plugin"/>
        </plugin>
      </plugins>
    

    输出如上图,这么看上去插件开发其实蛮简单的,我们继续往下看。

    2.Interceptor拦截器

    Interceptor接口,我们看名字,都能知道他是个拦截器。拦截器,其实我们先不看代码,拦截器的存在无外呼这两点:他要拦截什么?拦截下来要做什么?,这么一看,我们是不是敏感的察觉到动态代理的味道?

    下面看下他的具体方法。

    public interface Interceptor {
    
      //具体的拦截器业务逻辑
      Object intercept(Invocation invocation) throws Throwable;
    
      //生成代理对象
      default Object plugin(Object target) {
        return Plugin.wrap(target, this);
      }
    
      default void setProperties(Properties properties) {
        // NOP
      }
    
    }
    

    Interceptor的注册我们其实在之前的Configuration解析中已经提过了,这边再重温下。

    private void parseConfiguration(XNode root) {
        try {
          ...
          //解析plugins节点(注册interceptorChain里记录对应的拦截器)
          pluginElement(root.evalNode("plugins"));
          ...
        } catch (Exception e) {
          throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
        }
      }
    
      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 interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();
            interceptorInstance.setProperties(properties);
            configuration.addInterceptor(interceptorInstance);
          }
        }
      }
    

    代码很简单,就不解释了,主要就是解析xml中Interceptor,反射实例化生成放入configuration中属性interceptorChain中。

    下面我们看下这个interceptorChain,从字面上我们就知道他是个拦截链,也就是个拦截器的集合。

    public class InterceptorChain {
    
      private final List<Interceptor> interceptors = new ArrayList<>();
    
      //生成代理对象
      public Object pluginAll(Object 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);
      }
    
    }
    

    这里面最重要的就是pluginAll方法,他主要是调用plugin生成代理对象。我们看下pluginAll方法到底有哪些地方用到了?

    也就是对应的我们到底要代理哪些对象,也就是回答之前的问题,我们到底要拦截哪些方法?

      public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
        ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
        //又见到老朋友插件的过滤器链
        parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
        return parameterHandler;
      }
    
      public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
          ResultHandler resultHandler, BoundSql boundSql) {
        ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
        //又见到老朋友插件的过滤器链
        resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
        return resultSetHandler;
      }
    
      public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
        //根据不同的type生成不同的StatementHandler
        StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
        //又见到老朋友插件的过滤器链
        statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
        return statementHandler;
      }
    
      public Executor newExecutor(Transaction transaction) {
        return newExecutor(transaction, defaultExecutorType);
      }
    
      public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
        executorType = executorType == null ? defaultExecutorType : executorType;
        executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
        Executor executor;
        //典型的工厂模式,三种类型的executor
        if (ExecutorType.BATCH == executorType) {
          //批量执行器
          executor = new BatchExecutor(this, transaction);
        } else if (ExecutorType.REUSE == executorType) {
          //可重用执行器(用到了享元模式,用到statementMap来缓存同一生命周期内的statement。)
          executor = new ReuseExecutor(this, transaction);
        } else {
          //默认的简单执行器
          executor = new SimpleExecutor(this, transaction);
        }
        //开启二级缓存的话,再外面再包一层CachingExecutor,装饰者模式
        if (cacheEnabled) {
          executor = new CachingExecutor(executor);
        }
        //插件机制,其实就是一个动态代理的拦截器链,用到了一个责任链模式
        executor = (Executor) interceptorChain.pluginAll(executor);
        return executor;
      }
    

    总结一波,一共代理了四种对象:

    • ParameterHandler(拦截参数)
    • ResultSetHandler(拦截结果集)
    • StatementHandler(拦截Statement)
    • Executor(拦截Executor执行器)

    也就对应的我们之前的拦截器头上的注解type:

    3.Plugin

    下面我们看下是如何具体生成代理对象的。

    public interface Interceptor {
      ...
      //生成代理对象
      default Object plugin(Object target) {
        return Plugin.wrap(target, this);
      }
      ...
    }
    
    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) {
        //根据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;
      }
      //代理逻辑
      @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
          //拿出signatureMap中对应的methods集合
          Set<Method> methods = signatureMap.get(method.getDeclaringClass());
          //当前执行的方法是否在拦截器中
          if (methods != null && methods.contains(method)) {
            //执行具体的拦截器业务逻辑(譬如之前的"hello plugin")
            return interceptor.intercept(new Invocation(target, method, args));
          }
          return method.invoke(target, args);
        } catch (Exception e) {
          throw ExceptionUtil.unwrapThrowable(e);
        }
      }
      //根据interceptor上面定义的注解,获取需要拦截的信息
      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对象
        Signature[] sigs = interceptsAnnotation.value();
        Map<Class<?>, Set<Method>> signatureMap = new HashMap<>();
        for (Signature sig : sigs) {
          //生成signatureMap,key=sig.type(),value=Methods集合
          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;
      }
    
      //返回需要拦截的接口信息,一直往上查
      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<?>[0]);
      }
    
    }
    
    

    注释写的很清楚,基本上这一切都串上来了。

    4.Invocation

    最后就生下一个问题,拦截器链的具体实现。譬如:

      <plugins>
        <plugin interceptor="org.apache.ibatis.builder.ExamplePlugin">
          <property name="pluginProperty" value="hello plugin"/>
        </plugin>
        <plugin interceptor="org.apache.ibatis.builder.Example2Plugin">
          <property name="pluginProperty" value="hello plugin2"/>
        </plugin>
      </plugins>
    

    首先所以我们发现对代理的对象,我们是有一个多次代理的操作。

    public class InterceptorChain {
      ...
    
      public Object pluginAll(Object target) {
        for (Interceptor interceptor : interceptors) {
          target = interceptor.plugin(target);
        }
        return target;
      }
      ...
    }
    
      //代理逻辑
      @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
          //拿出signatureMap中对应的methods集合
          Set<Method> methods = signatureMap.get(method.getDeclaringClass());
          //当前执行的方法是否在拦截器中
          if (methods != null && methods.contains(method)) {
            //执行具体的拦截器业务逻辑(譬如之前的"hello plugin")
            return interceptor.intercept(new Invocation(target, method, args));
          }
          return method.invoke(target, args);
        } catch (Exception e) {
          throw ExceptionUtil.unwrapThrowable(e);
        }
      }
    
    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 proceed() throws InvocationTargetException, IllegalAccessException {
        return method.invoke(target, args);
      }
    
    }
    

    上面的Invocation对应的proceed就是形成链的关键方法,他是返回上次代理的处理方法。所以在所有自定义的Interceptor的处理逻辑的最后一句都会返回这个方法,以此来调用上层的拦截逻辑。

    此时的target就是上一层的Plugin对象。

    所以综上所述,如果有多个拦截器,他是一个嵌套的过程,被代理的对象是上次生成的代理对象,以此类推,形成一个链条的形式。

  • 相关阅读:
    关于Windows程序设计的初步认识
    C++虚函数作用原理(二)——类的继承
    史诗级Java资源大全中文版
    马上加薪!测试,你的职业发展...
    你不知道的接口自动化测试!
    69道Spring面试题和答案,简单明了无套路
    大厂都在问的多线程面试题,你不了解下嘛?
    现代Java进阶之路必备技能——2019 版
    80后程序员降薪6K,预感中年危机来袭,准备跳槽却碰壁
    微服务、分布式、高并发都不懂,你拿什么去跳槽?
  • 原文地址:https://www.cnblogs.com/zhou-yuan/p/14627031.html
Copyright © 2020-2023  润新知