原理

使用的是责任链模式,在四大对象调度的时候,使用插件执行代码,去执行操作
即初始化的时候在Config对象中,由于是单例,全局唯一,生成的插件对象保存进入数组中,这个插件需要实现一个接口,然后在使用插件的时候,通过责任链的方式,循环调用,进行处理.
核心在于下面这一段

  public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target);
    }
    return target;
  }

使用插件

使用插件需要实现接口Interceptor

package com.ming.MyBatis;

import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Invocation;

import java.util.Properties;

public class PluginElement implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        return null;
    }

    @Override
    public Object plugin(Object target) {
        return null;
    }

    @Override
    public void setProperties(Properties properties) {

    }
}

插件代理和反射设计

首先进入执行器

    executor = (Executor) interceptorChain.pluginAll(executor);

然后执行器执行在这

  public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target);
    }
    return target;
  }

plugin为生成代理对象的方法,逐步传递.
一层一层拦截处理

MyBatis提供了插件类,用来生成代理对象,用来上方循环

/**
 *    Copyright 2009-2019 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
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 {

  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) {
    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 {
      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);
    }
  }

  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;
  }

  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()]);
  }

}

这是一个动态代理对象.
wrap是生成的动态代理对象.
invoke是调度的方法.
如果存在拦截签名,将会在这里调用
若不存在拦截签名,将会直接调用原生的方法

插件开发

看上方的代码,可以知道需要确定拦截签名用来调用开发好的插件
如果需要拦截StatementHandler 执行SQL的过程

需要先查看StatementHandler接口

/**
 *    Copyright 2009-2016 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.executor.statement;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;

import org.apache.ibatis.cursor.Cursor;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.session.ResultHandler;

/**
 * @author Clinton Begin
 */
public interface StatementHandler {

  Statement prepare(Connection connection, Integer transactionTimeout)
      throws SQLException;

  void parameterize(Statement statement)
      throws SQLException;

  void batch(Statement statement)
      throws SQLException;

  int update(Statement statement)
      throws SQLException;

  <E> List<E> query(Statement statement, ResultHandler resultHandler)
      throws SQLException;

  <E> Cursor<E> queryCursor(Statement statement)
      throws SQLException;

  BoundSql getBoundSql();

  ParameterHandler getParameterHandler();

}

开发插件需要实现Interceptor接口
通过看接口定义可以知道需要拦截

  Statement prepare(Connection connection, Integer transactionTimeout)
      throws SQLException;

方法

所以编写注解,提供签名如下

// 说明这是拦截器
@Intercepts({
        // 需要拦截的方法
        @Signature(
                type = StatementHandler.class,
                method = "prepare",
                args = {
                        // 提供参数
                        Connection.class
                }
        )
})

下面需要实现该接口

package com.ming.MyBatis.plugin;

import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;

import java.util.Properties;


/**
 * 对数据库返回的数据进行限制插件
 * @author ming
 */
// 说明这是拦截器
@Intercepts({
        // 需要拦截的方法
        @Signature(
                type = Executor.class,
                method = "update",
                args = {
                        // 提供参数
                        MappedStatement.class,
                        Object.class
                }
        )
})
public class MyPlugin implements Interceptor {
    Properties properties = null;
    /**
     * 用于替换拦截方法的内容,在这里,原始方法被拦截
     * @param invocation  此为处理该内容的责任链
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 处理之前
        System.out.println("before.....");
        // 进行调用真实的对象,否则调度到下一个代理对象invoke方法
        Object object = invocation.proceed();
        // 处理之后
        System.out.println("after....");
        return object;
    }

    /**
     * 生成对象代理,用于源码中的循环 即调用的是这一句 target = interceptor.plugin(target);
     * 在这里,targer为插件对象,然后在这里进行循环处理,使用默认的wrap进行调用拦截的方法
     * @param target
     * @return
     */
    @Override
    public Object plugin(Object target) {
        System.out.println("调用生成代理对象");
        // 使用的是wrap方法,用于生成代理对象
        return Plugin.wrap(target, this);
    }

    /**
     * 从配置文件中,获取配置属性 初始化的时候会调用此方法
     * @param properties
     */
    @Override
    public void setProperties(Properties properties) {
        System.out.println(properties.get("dbType"));
        this.properties = properties;
    }
}

一个是用来调用真实方法的.一个是用来生成代理对象的时候循环生成.最后是读取内容
书写完成以后更改配置文件

	<plugins>
		<plugin interceptor="com.ming.MyBatis.plugin.MyPlugin">
			<property name="dbType" value="mysql"/>
		</plugin>
	</plugins>

然后运行.
查看日志信息,可以看到

[2019-04-21 01:16:41,506] Artifact mingmingxiao:war exploded: Artifact is deployed successfully
[2019-04-21 01:16:41,506] Artifact mingmingxiao:war exploded: Deploy took 2,203 milliseconds
mysql
调用生成代理对象
调用生成代理对象
调用生成代理对象
调用生成代理对象
调用生成代理对象
21-Apr-2019 01:16:49.028 信息 [Catalina-utility-1] org.apache.catalina.startup.HostConfig.deployDirectory 把web 应用程序部署到目录 [/home/ming/apache-tomcat-9.0.17/webapps/manager]
21-Apr-2019 01:16:49.107 信息 [Catalina-utility-1] org.apache.catalina.startup.HostConfig.deployDirectory Deployment of web application directory [/home/ming/apache-tomcat-9.0.17/webapps/manager] has finished in [79] ms
调用生成代理对象

在这里,完成一个插件的开发