MyBatis插件(拦截器)其实不难,我们使用通俗易懂的话来深入理解插件原理

文章目录

1.背景

1.1.我们怎么去理解【插件】这个单词

1.大家应该都有笔记本电脑,不同的笔记电脑上或多或少的有一些插槽(例如下图);
  我们都知道,这些插槽实际上是辅助或增强我们电脑的功能的;
  比如:USB接口:我们可以使用USB接口连接鼠标,使得我们通过鼠标去控制屏幕的操作; 
               如果我们电脑自带的硬盘磁盘空间不足了,我们也可以通过使用USB接口,外接一个硬盘,满足硬盘不足的问题
       网线接口:我们电脑本身可以使用无线网进行上网,同时我们也可以通过这个网线接口进行有线上网;    
2.这些插口,我们都认为是插件;      

在这里插入图片描述

1.2.总结

1.从上面的案例介绍,我们知道,插件其实本质上是增强了对象的功能,是不是有点动态代理的作用?

2.常规在没有插件的情况下【MyBatis使用存在的问题】

2.1.入参SQL组装无法自动组装问题

1.比如我们希望调用到MySql进行执行SQL的时候,可能会使用limit这个脚本,我们希望下面的start和end根据我们传递的值去动态的产生执行SQL;
  select * from emp where id limit start,end;
  默认情况下,mybatis是不支持的;
2.这个时候,我们就可以使用插件,在SQL执行之前,去对SQL进行处理,产生新的SQL,再使用新的SQL执行  

2.2.在做数据库操作前后想做其他操作等等

1.如果我们希望在进行数据库操作的过程中,希望做一下其他的操作,这个过程可以是数据库操作前,也可以是数据库操作之后;

3.MyBatis插件源码

3.1.MyBatis插件本质【代理】

1.mybatis插件的本质实际是代理,下面我们开看一下插件类Plugin类;

3.2.Plugin

源码地址
https://gitee.com/gaoxinfu_admin/open-source/blob/master/mybatis/mybatis-3-master/src/main/java/org/apache/ibatis/plugin/Plugin.java

/**
 *    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;

/**
 * 参考 http://www.mybatis.org/mybatis-3/zh/configuration.html#plugins
 *
 * 四大拦截对象:
 * @see org.apache.ibatis.executor.Executor
 * @see org.apache.ibatis.executor.statement.StatementHandler
 * @see org.apache.ibatis.executor.parameter.ParameterHandler
 * @see org.apache.ibatis.executor.resultset.ResultSetHandler
 *
 * @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) {
      //jdk 动态代理
      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());
       /*
        拦截:
        这个地方通过 methods中是否包含改方法,判断是否需要拦截
         */
      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);
    }
  }

  /**
   *
   * @param interceptor
   * @return
   */
  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 就是Annotation注解中的内容
     */
    Signature[] sigs = interceptsAnnotation.value();
    Map<Class<?>, Set<Method>> signatureMap = new HashMap<>();
    for (Signature sig : sigs) {

      /**
       * signatureMap.computeIfAbsent(sig.type(), k -> new HashSet<>())
       * 相当于:
       *  Object key= signatureMap.get(sig.type());
       *  if (key==null){
       *    key=new HashSet<>();
       *    signatureMap.put(sig.type(),key);
       *  }
       */

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

}

3.2.1.构造方法:【构造一个对象,后面invoke直接使用对象里内容】

3.2.1.1.【code】
  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;
  }
3.2.1.2.【使用位置】

在这里插入图片描述

3.2.2.对被代理对象target进行包装,返回一个代理的对象Plugin【本质上jdk动态代理】

3.2.2.1.【code】
  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) {
      //jdk 动态代理
      return Proxy.newProxyInstance(
          type.getClassLoader(),
          interfaces,
          new Plugin(target, interceptor, signatureMap));
    }
    return target;
  }
3.2.2.2.【使用位置】

在这里插入图片描述
在这里插入图片描述
Example01PageInterceptor为例

在这里插入图片描述
在这里插入图片描述

3.2.3.代理对象的调用:【拦截器的处理】【Invoke->intercept()方法】

3.2.3.1【code】
 @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      Set<Method> methods = signatureMap.get(method.getDeclaringClass());
       /*
        拦截:
        这个地方通过 methods中是否包含改方法,判断是否需要拦截
         */
      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);
    }
  }
3.2.3.2【使用位置】
1.通过上面wrap方法的封装,我们知道,代理对象是一个plguin的对象,所以一旦target指定的方法一旦被调用,便会先进入plugin.invoke方法
  进而进入到intercept()方法

在这里插入图片描述

在这里插入图片描述

4.MyBatis拦截的四大对象

https://mybatis.org/mybatis-3/zh/configuration.html#plugins

4.1.Executor

4.1.1.方法:【update, query, flushStatements, commit, rollback, getTransaction, close, isClosed】

4.1.2.封装代理Executor的Code位置

4.1.2.1.【SecondLevelDemo.testNotCommit】

在这里插入图片描述

4.1.2.2.【DefaultSqlSessionFactory.openSession】

在这里插入图片描述

4.1.2.3.【DefaultSqlSessionFactory.openSessionFromDataSource】

在这里插入图片描述

4.1.2.4.【Configuration.newExecutor】

在这里插入图片描述

4.1.3.使用Executor的Code位置

4.1.3.1.【SecondLevelDemo.testNotCommit】

在这里插入图片描述

4.1.3.2.【DefaultSqlSession.getMapper】

在这里插入图片描述

4.1.3.3.【Configuration.getMapper】

在这里插入图片描述

4.1.3.4.【MapperRegistry.getMapper】

在这里插入图片描述

4.1.3.5.【MapperProxyFactory.newInstance】

最终返回的一个代理对象MapperProxy
在这里插入图片描述

4.1.3.6.【SecondLevelDemo.testNotCommit】

在这里插入图片描述

4.1.3.7.【MapperProxy.invoke】

在这里插入图片描述

4.1.3.8.【MapperMethod.execute】

在这里插入图片描述

4.1.3.9.【DefaultSqlSession.selectOne】

在这里插入图片描述

4.1.3.10.【DefaultSqlSession.selectList】

在这里插入图片描述

4.1.3.11.【Plugin.invoke】

在这里插入图片描述

4.1.3.12.【Example01PageInterceptor.intercept】

在这里插入图片描述

4.2.StatementHandler

4.2.1.方法:【prepare, parameterize, batch, update, query】

4.2.2.封装StatementHandler代理的code位置

4.2.2.1.【SecondLevelDemo.testNotCommit】

在这里插入图片描述

4.2.2.2.【MapperProxy.invoke】

在这里插入图片描述

4.2.2.3.【MapperMethod.execute】

在这里插入图片描述

4.2.2.4.【DefaultSqlSession.selectOne】

在这里插入图片描述

4.2.2.5.【DefaultSqlSession.selectList】

在这里插入图片描述

4.2.2.7.【BaseExecutor.query】

在这里插入图片描述
在这里插入图片描述

4.2.2.8.【BaseExecutor.queryFromDatabase】

我们先看最普通的SimpleExecutor
在这里插入图片描述

4.2.2.9.【SimpleExecutor.doQuery】

在这里插入图片描述

4.2.2.10.【Configuration.newStatementHandler】

在这里插入图片描述

4.2.3.使用SimpleHandler的Code位置

其实我们只要找handler.方法即可,当然还有其他的,我们这里不再赘述,标记
在这里插入图片描述

4.3.ParameterHandler

4.3.1.方法:【getParameterObject, setParameters】

4.3.2.封装ParameterHandler代理的位置

4.3.2.1.【SimpleExecutor.doQuery】

在这里插入图片描述

4.3.2.2.【Configuration.newStatementHandler】

在这里插入图片描述

4.3.2.3.【RoutingStatementHandler.RoutingStatementHandler()】

在这里插入图片描述

4.3.2.4.【SimpleStatementHandler.SimpleStatementHandler()】

在这里插入图片描述

4.3.2.6.【BaseStatementHandler.BaseStatementHandler()】

在这里插入图片描述

4.3.2.7.【Configuration.newParameterHandler】

在这里插入图片描述

4.3.3.使用ParameterHandler的Code位置

在这里插入图片描述

4.4.ResultSetHandler

4.4.1.方法:【andleResultSets, handleOutputParameters】

4.4.2.封装ResultSetHandler代理的位置

在这里插入图片描述

在这里插入图片描述

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

东山富哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值