MyBatis-插件模块

1、Interceptor

1.1、接口源码分析如下:
package org.apache.ibatis.plugin;

import java.util.Properties;

/**
 * MyBatis允许用户使用自定义拦截器对SQL语句执行的某一点进行拦截。
 * 默认情况下,MyBatis允许拦截器拦截Excerutor的方法、ParameterHandler
 * 的方法、ReslultSetHanlder的方法以及StatementHandler的方法。具体拦截
 * 的方法入下:
 * 1)Excutor中的update()方法、query()方法、flushStatement()方法、commit()方法、
 * rollback()方法、getTransaction()方法、close()方法、isClosed()方法。
 * 2)ParameterHadler中的getParameterObject()方法、setParameters()方法。
 * 3)ResultSetHandler中的handlerResultSets()方法、handleOutputParamenters()方法。
 * 4)SatementHandler中的prepare()方法、parameterize()方法、batch()方法、update()方法、
 * query()方法。
 *
 * @author Clinton Begin
 */
public interface Interceptor {

  // 执行拦截逻辑的方法
  Object intercept(Invocation invocation) throws Throwable;

  // 决定是否触发 intercept ()方法
  default Object plugin(Object target) {
    return Plugin.wrap(target, this);
  }

  // 根据配置初始化 Interceptor 对象
  default void setProperties(Properties properties) {
    // NOP
  }

}
1.2、自定义使用的方法:

​ 用户自定义的拦截器除了继承Interceptor接口,还需要使用@Intercepts和@Signature两个注解进行标识。@Intercepts注解中指定了一个@Signature注解列表,每个注解中都标识了该插件需要拦截的方法信息,其中@Signature注解的type属性指定需要拦截的类型,method属性指定需要拦截的方法,args属性指定了被拦截方法的参数列表。通过这三个属性值,@Signature注解就可以表示一个方法签名,唯一确定一个方法。如下所示,该拦截器需要拦截Executor接口的两个方法,分别是query和close方法。

@Intercepts({
	@Signature(type =Executor . class , method =”query”, args = {
		MappedStatement.class , Object.class , RowBounds.class ,
		ResultHandler . class}) ,
	@Signature ( type =Executor . class , method = ” close”, args = {boolean.class}
})
public class ExamplePlugin implements Interceptor {
	private int testProp ; //省略该属性的 getter/setter 方法
	// .. .其他方法在后面详细介
}

​ 定义完成一个自定义拦截器之后,需要在mybatis-config.xml配置文件中对该拦截器进行配置,如下所示:

<plugins>
	<plugin interceptor="org.apache.ibatis.builder.ExamplePlugin">
		<property name="pluginProperty" value="100"/>
	</plugin>
</plugins>
1.3、加载和使用原理分析

​ 在MyBatis初始化时,会通过XMLConfigBuilder.pluginElement()方法解析mybatis-config.xml配置文件中定义的节点,得到相应的Interceptor对象以及调用Interceptor.setProperties(properties)方法配置相应属性,最后将Interceptor对象添加到Configuration.interceptorChain字段中保存。

// mybatis插件列表
protected final InterceptorChain interceptorChain = new InterceptorChain(); 

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

// Configuration.java

​ 完成InterceptoConfiguration.javar的加载操作之后,在MyBatis中使用的这四类对象,都是通过Configuration.new*()系列方法创建的。如果配置了用户自定义拦截器,则会在该系列方法中,通过InterceptorChain.pluginAll()方法为目标对象创建代理对象,所以通过Configuration.new*()系列方法得到的对象实际是一个代理对象。

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) {
  // 创建RoutingStatementHandler对象,实际由statementType来指定真实的StatementHandler来实现
  StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
  statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
  return statementHandler;
}

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
  executorType = executorType == null ? defaultExecutorType : executorType;
  executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
  Executor executor;
  if (ExecutorType.BATCH == executorType) {// 批处理执行器
    executor = new BatchExecutor(this, transaction);
  } else if (ExecutorType.REUSE == executorType) {// 可重复用的执行器
    executor = new ReuseExecutor(this, transaction);
  } else {
    executor = new SimpleExecutor(this, transaction);
  }
  // cacheEnabled默认是true,传入的参数是SimpleExecutor,装饰成CachingExecutor
  if (cacheEnabled) {// 如果配置了二级缓存
    executor = new CachingExecutor(executor);
  }
  executor = (Executor) interceptorChain.pluginAll(executor);
  return executor;
}

// Configuration.java

​ InterceptorChain.pluginAll()源码如下:

public Object pluginAll(Object target) {
  for (Interceptor interceptor : interceptors) {
    target = interceptor.plugin(target);// 调用Interceptor.plugin方法生成代理对象
  }
  return target;
}

​ 在Interceptor接口中有默认方法plugin,它使用了Mybatis提供的Plugin工具类来生成代理类,整个类源码分析如下:

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;// Interceptor 对象
  private final Map<Class<?>, Set<Method>> signatureMap;// 记录 @Signature 注解中的信息

  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 Signature 注解的信息,
    // getSignatureMap ()方法负责处理@Signature 注解
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    Class<?> type = target.getClass();// 获取目标类型
    // 获取 目标类型实现的接口,正如前文所述,拦截器可以拦截的 类对象都实现了相应的接口,这也是能
    // 使用 JDK 动态代理的方式创建代理对象的基础
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    if (interfaces.length > 0) {// 使用 JDK 动态代理的方式创建代理对象
      return Proxy.newProxyInstance(
          type.getClassLoader(),
          interfaces,
          // 因为Plugin实现了InvocationHandler对象
          new Plugin(target, interceptor, signatureMap));
    }
    return target;
  }

  /**
   * Plugin.invoke() 方法中,会将当前调用的方法与 signatureMap 集合中记录的方法信息进行
   * 比较,如果当前调用 的方法是需要被拦截的方法,则调用其 intercept()方法进行处理,如果不能
   * 被拦截 直接调用 target 的相应方法。
   */
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      // 获取当前方法所在类或接口中,可被当前 Interceptor 拦截的方法
      Set<Method> methods = signatureMap.get(method.getDeclaringClass());
      // 如采当前调用的方法需妥被拦截,则调用Interceptor.intercept()方法进行拦截处理
      if (methods != null && methods.contains(method)) {
      return interceptor.intercept(new Invocation(target, method, args));
    }
      // 如采当前调用的方法不能被拦截 9J1J 调用 target 对象的相应方法
      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()]);
  }

}

2、应用场景分析

2.1、分页插件

​ 使用MyBatis-PageHelper来分析,请查看下一小节。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值