Mybatis插件

文章目录

  • [1. 如何自定义插件](#1. 如何自定义插件)
    • [1.1 创建接口Interceptor的实现类](#1.1 创建接口Interceptor的实现类)
    • [1.2 配置拦截器](#1.2 配置拦截器)
    • [1.3 运行程序](#1.3 运行程序)
  • [2. 插件原理](#2. 插件原理)
    • [2.1 解析过程](#2.1 解析过程)
    • [2.2 创建代理对象](#2.2 创建代理对象)
      • [2.2.1 Executor](#2.2.1 Executor)
      • [2.2.2 StatementHandler](#2.2.2 StatementHandler)
      • [2.2. 3ParameterHandler](#2.2. 3ParameterHandler)
      • [2.2.4 ResultSetHandler](#2.2.4 ResultSetHandler)
    • [2.3 执行流程](#2.3 执行流程)

1. 如何自定义插件

1.1 创建接口Interceptor的实现类

java 复制代码
/**
 * @author Clinton Begin
 */
public interface Interceptor {

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

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

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

}

mybatis运行拦截的内容包括:

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)

定义一个实现类。

java 复制代码
/**
 * MyBatis中的自定义的拦截器
 *
 * @Signature 表示一个方法签名,唯一确定一个方法
 */
@Intercepts(
        {@Signature(
                type = Executor.class, // 拦截类型
                method = "query", // 拦截方法
                // args 中指定 被拦截方法的 参数列表
                args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
                @Signature(
                        type = Executor.class,
                        method = "close",
                        args = {boolean.class})
        })
public class MyInterceptor implements Interceptor {

    private String interceptorName;

    public String getInterceptorName() {
        return interceptorName;
    }

    /**
     * 执行拦截的方法
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        System.out.println("------MyInterceptor  before---------");
        Object proceed = invocation.proceed();
        System.out.println("------MyInterceptor  after---------");
        return proceed;
    }

    @Override
    public Object plugin(Object target) {
        return Interceptor.super.plugin(target);
    }

    @Override
    public void setProperties(Properties properties) {
        System.out.println("setProperties : " + properties.getProperty("interceptorName"));
        this.interceptorName = properties.getProperty("interceptorName");
    }
}

1.2 配置拦截器

xml 复制代码
 <plugins>
        <plugin interceptor="com.boge.interceptor.MyInterceptor">
            <property name="interceptorName" value="myInterceptor"/>
        </plugin>
 </plugins>

1.3 运行程序

java 复制代码
    @Test
    public void test2() throws Exception{
        // 1.获取配置文件
        InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
        // 2.加载解析配置文件并获取SqlSessionFactory对象
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
        // 3.根据SqlSessionFactory对象获取SqlSession对象
        SqlSession sqlSession = factory.openSession();
        // 4.通过SqlSession中提供的 API方法来操作数据库
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        Integer param = 1;
        User user = mapper.selectUserById(param);
        System.out.println(user);
}

拦截的query方法和close方法的源码位置在如下:


2. 插件原理

2.1 解析过程

解析全局配置文件过程中,查看XMLConfigBuilder类的方法parseConfiguration。

java 复制代码
private void pluginElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        // 获取<plugin> 节点的 interceptor 属性的值
        String interceptor = child.getStringAttribute("interceptor");
        // 获取<plugin> 下的所有的properties子节点
        Properties properties = child.getChildrenAsProperties();
        // 获取 Interceptor 对象
        Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();
        // 设置 interceptor的 属性
        interceptorInstance.setProperties(properties);
        // Configuration中记录 Interceptor
        configuration.addInterceptor(interceptorInstance);
      }
    }
  }

该方法主要创建Interceptor 对象,并设置属性,最终放在configuration对象的InterceptorChain里面

来看InterceptorChain的源码。

java 复制代码
/**
 * InterceptorChain 记录所有的拦截器
 * @author Clinton Begin
 */
public class InterceptorChain {

  // 保存所有的 Interceptor  也就我所有的插件是保存在 Interceptors 这个List集合中的
  private final List<Interceptor> interceptors = new ArrayList<>();

  // 现在我们定义的有一个 Interceptor MyInterceptor
  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);
  }

}

可以看到拦截器放在这个list变量interceptors 。

2.2 创建代理对象

2.1步骤创建了拦截器,并且保存在InterceptorChain,**那拦截器如何与目标对象关联?**拦截器拦截对象包括:Executor,ParameterHandler,ResultSetHandler,StatementHandler. 这些对象创建的时候需要注意什么?

2.2.1 Executor

在创建SqlSession的过程中,会创建执行器Executor。可以看到Executor植入插件

java 复制代码
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) { // 针对 Statement 对象做缓存
      executor = new ReuseExecutor(this, transaction);
    } else {
      // 默认 SimpleExecutor 每一次只是SQL操作都创建一个新的Statement对象
      executor = new SimpleExecutor(this, transaction);
    }
    // 二级缓存开关,settings 中的 cacheEnabled 默认是 true
    // 映射文件中 <cache> 标签 --> 创建 Cache对象
    // settings 中的 cacheEnabled = true 真正的对 Executor 做了缓存的增强
    if (cacheEnabled) {
      // 穿衣服的事情 --> 装饰器模式
      executor = new CachingExecutor(executor);
    }
    // 植入插件的逻辑,至此,四大对象已经全部拦截完毕
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

进入pluginAll方法:

java 复制代码
 // 现在我们定义的有一个 Interceptor MyInterceptor
  public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) { // 获取拦截器链中的所有拦截器
      target = interceptor.plugin(target); // 创建对应的拦截器的代理对象
    }
    return target;
  }

再进入plugin方法


再查看Plugin工具类的实现 wrap方法。

java 复制代码
  /**
   * 创建目标对象的代理对象
   *    目标对象 Executor  ParameterHandler  ResultSetHandler StatementHandler
   * @param target 目标对象
   * @param interceptor 拦截器
   * @return
   */
  public static Object wrap(Object target, Interceptor interceptor) {
    // 获取用户自定义 Interceptor中@Signature注解的信息
    // getSignatureMap 负责处理@Signature 注解  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;
  }

getSignatureMap方法

再来看Plugin的源码。

java 复制代码
/**
 * @author Clinton Begin
 */
public class Plugin implements InvocationHandler {

  private final Object target; // 目标对象
  private final 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;
  }

  /**
   * 创建目标对象的代理对象
   *    目标对象 Executor  ParameterHandler  ResultSetHandler StatementHandler
   * @param target 目标对象
   * @param interceptor 拦截器
   * @return
   */
  public static Object wrap(Object target, Interceptor interceptor) {
    // 获取用户自定义 Interceptor中@Signature注解的信息
    // getSignatureMap 负责处理@Signature 注解  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;
  }

  /**
   * 代理对象方法被调用时执行的代码
   * @param proxy
   * @param method
   * @param args
   * @return
   * @throws Throwable
   */
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      // 获取当前方法所在类或接口中,可被当前Interceptor拦截的方法 Executor query
      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);
    }
  }

  /**
   * 获取拦截器中的 @Intercepts 注解中的相关内容
   * @param interceptor
   * @return
   */
  private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
    // 获取 @Intercepts 注解
    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) {
      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()) {
        // 判断 目标对象的 接口类型是否在 @Signature 注解中声明的有
        if (signatureMap.containsKey(c)) {
          interfaces.add(c);
        }
      }
      // 继续获取父类
      type = type.getSuperclass();
    }
    return interfaces.toArray(new Class<?>[interfaces.size()]);
  }

}

2.2.2 StatementHandler


2.2. 3ParameterHandler


2.2.4 ResultSetHandler


2.3 执行流程

相关推荐
凌辰揽月1 小时前
AJAX 学习
java·前端·javascript·学习·ajax·okhttp
永日456701 小时前
学习日记-spring-day45-7.10
java·学习·spring
小屁孩大帅-杨一凡3 小时前
如何解决ThreadLocal内存泄漏问题?
java·开发语言·jvm·算法
24kHT3 小时前
xml映射文件的方式操作mybatis
xml·mybatis
学习3人组3 小时前
在 IntelliJ IDEA 系列中phpstorm2025设置中文界面
java·ide·intellij-idea
cainiao0806055 小时前
Java 大视界:基于 Java 的大数据可视化在智慧城市能源消耗动态监测与优化决策中的应用(2025 实战全景)
java
长风破浪会有时呀5 小时前
记一次接口优化历程 CountDownLatch
java
云朵大王5 小时前
SQL 视图与事务知识点详解及练习题
java·大数据·数据库
我爱Jack6 小时前
深入解析 LinkedList
java·开发语言
27669582927 小时前
tiktok 弹幕 逆向分析
java·python·tiktok·tiktok弹幕·tiktok弹幕逆向分析·a-bogus·x-gnarly