mybatis3 源码深度解析-mybatis插件实现原理分析-jdk动态代理

大纲

  • mybatis 插件使用
  • mybatis 插件涉及相关类介绍
  • mybatis 插件实现原理
  • 自定义分页分页插件

mybatis 插件使用

mybatis 插件通过实现自定义拦截器的方式可以改变 sql 的执行行为

//1 编写自定义插件
@Intercepts({// 使用这个注解,声明这是一个 mybaits拦截器  
  //要拦截方法签名(type:被拦截方法所在的类,method:被拦截方法名,args:被拦截方法参数列表类型
  @Signature(type= Executor.class,method = "update",args = {MappedStatement.class,Object.class})
})
public class ExamplePlugin implements Interceptor {
  private Properties properties = new Properties();
  @Override
  public Object intercept(Invocation invocation) throws Throwable {
    // implement pre processing if need
    Object returnObject = invocation.proceed();
    // implement post processing if need
    return returnObject;
  }

  @Override
  public void setProperties(Properties properties) {
    this.properties = properties;
  }
}



  
    
  

mybatis 插件涉及相关类介绍

  • Configuration 类中维护了InterceptorChain拦截器链对象
  • 在 Configuration 解析创建过程中会解析 标签,将插件维护到interceptorChain
public class Configuration {
  // mybatis插件列表  List interceptors = new ArrayList<>();
  protected final InterceptorChain interceptorChain = new InterceptorChain();
  //在 Configuration 解析创建过程中会解析 标签,将插件维护到interceptorChain
  public void addInterceptor(Interceptor interceptor) {
    interceptorChain.addInterceptor(interceptor);
  }
}
//InterceptorChain对象 是一个拦截器链对象
public class InterceptorChain {
  private final List interceptors = new ArrayList<>();
 //遍历调用所有拦截器对象的 plugin()方法执行拦截逻辑
 //target 是传过来的执行器等4大天王(Executor,ParameterHandler,ResultSetHandler,StatementHandler)
  public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
        // 使用jdk 创建executor的代理对象 ,循环 将target作为入参,一个嵌套一个target (责任链模式)
      target = interceptor.plugin(target);
    }
      //责任链 后面的代理executor,持有前面代理executor的引用
    return target;
  }
  • 默认情况下,MyBatis 允许使用插件来拦截的以下 4 个组件方法调用:

Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)

ParameterHandler (getParameterObject, setParameters)

ResultSetHandler (handleResultSets, handleOutputParameters)

StatementHandler (prepare, parameterize, batch, update, query)

  • MyBatis 为什么能实现对以上方法的拦截? Configuration类提供的工厂方法,实现了拦截逻辑
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
  ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
  //调用interceptorChain.pluginAll(),创建ParameterHandler代理对象
  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);
  //调用interceptorChain.pluginAll(),创建代理对象
  resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
  return resultSetHandler;
}

public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
  StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
   //调用interceptorChain.pluginAll(),创建代理对象
  statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
  return statementHandler;
}

public Executor newExecutor(Transaction transaction) {
  //return newExecutor(transaction, defaultExecutorType);
  	//调用interceptorChain.pluginAll(),创建代理对象 ...
   //executor = (Executor) interceptorChain.pluginAll(executor);
}
  • 了解 Interceptor接口
/**
 * 插件在具体实现的时候,采用的是拦截器模式,要注册为mybatis插件,必须实现org.apache.ibatis.plugin.Interceptor接口
 * interceptor属性值既可以完整的类名,也可以是别名,只要别名在typealias中存在即可,如果启动时无法解析,会抛出ClassNotFound异常。
 */
public interface Interceptor {
  /**
   * @param invocation 封装目标对象的方法和参数
   * @return
   */
  Object intercept(Invocation invocation) throws Throwable;
  /**
   * 用于创建target 目标对象的的代理对象 (所以方法名更适合叫 createPluginProxy?)
   * @param target
   * @return
   */
  default Object plugin(Object target) {
    // 使用jdk 创建executor的代理对象
    return Plugin.wrap(target, this);
  }
  //用于设置插件的属性值
  default void setProperties(Properties properties) {
    // NOP
  }
}
//invocation 封装目标对象的方法和参数
public class Invocation {
  //目标对象 传过来的执行器等4大天王(Executor, ParameterHandler, ResultSetHandler, StatementHandler)
  private final Object target;
  //目标方法,拦截的方法
  private final Method method;
  //入参
  private final Object[] args;
  //执行目标方法
  public Object proceed() throws InvocationTargetException, IllegalAccessException {
    return method.invoke(target, args);
  }
}
  • 为了创建目标对象 传过来的执行器等4大天王(Executor, ParameterHandler, ResultSetHandler, StatementHandler) 的代理对象,mybatis 提供了一个 Plugin工具类(又或者叫PluginProxy?)
  • java实现动态代理的两种方式-jdk 和 cglib

在学习 jdk 动态代理的时候 ,了解到动态代理涉及以下 3 个主体

1 目标对象(被代理的对象)必须要实现业务接口, mybatis插件中的业务接口为 Interceptor

2 代理对象(代理类):必须实现 InvocationHandler 接口, mybatis插件中的代理对象为Plugin

3 使用 Proxy.newProxyInstance() 创建代理对象, 这里直接将创建代理对象的工具工厂方法写在了Plugin.wrap()方法中

public class Plugin implements InvocationHandler {
  private final Object target;
  private final Interceptor interceptor;
  private final Map, Set> signatureMap;

  /**
   * 代理工厂方法: 用于为目标对象生成jdk 动态代理对象
   * @param target 目标对象: 传过来的执行器等4大天王(Executor, ParameterHandler, ResultSetHandler, StatementHandler) 
   * @param interceptor PluginProxy 代理对象本身
   * @return
   */
  public static Object wrap(Object target, Interceptor interceptor) {
    //如果方法是@Intercepts注解中指定的方法,则执行拦截逻辑
    Map, Set> signatureMap = getSignatureMap(interceptor);
    //CacheExecutor 对象对应的类
    Class<?> type = target.getClass();
    //获取类型对应的接口 org.apache.ibatis.executor.Executor
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    if (interfaces.length > 0) {
      // 使用jdk 创建executor的代理对象
      return Proxy.newProxyInstance(
        type.getClassLoader(),
        interfaces,
        new Plugin(target, interceptor, signatureMap));
    }
    return target;
  }

  //在 jdk 动态代理中,所有代理对象会先执行invoke()
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      Set 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);
    }
  }
}

mybatis 插件实现原理

SqlSession会调用 Configuration.newExecutor() 工厂方法创建 Executor 对象,经过interceptorChain.pluginAll()方法后其实已经是一个 jdk生成的动态代理对象了

//1 SqlSession会调用 Configuration.newExecutor() 工厂方法创建 Executor 对象
public Executor newExecutor(Transaction transaction) {
  //return newExecutor(transaction, defaultExecutorType);
  	//2.1 调用interceptorChain.pluginAll(),创建代理对象 ...
   // executor = (Executor) interceptorChain.pluginAll(executor);
}

public class InterceptorChain {
  private final List interceptors = new ArrayList<>();
  public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      // 2.2 使用jdk 创建executor的代理对象 ,循环 将target作为入参,一个嵌套一个target (责任链模式)
      target = interceptor.plugin(target);
    }
    // 5 使用 for 循环遍历形成链链表 后面的代理executor,持有前面代理executor的引用
    //代理对象 :plugin3 → plugin2 → Plugin1 → Executor
    return target;
  }

public interface Interceptor {
  Object intercept(Invocation invocation) throws Throwable;
  default Object plugin(Object target) {
    // 3 Interceptor实现类,调用Plugin.wrap()静态方法,创建代理对象
    return Plugin.wrap(target, this);
  }
} 

public class Plugin implements InvocationHandler {
  private final Object target;
  private final Interceptor interceptor;

  /**
   * 4 代理工厂方法: 用于为目标对象生成jdk 动态代理对象
   * @param target 目标对象: 传过来的执行器等4大天王(Executor, ParameterHandler, ResultSetHandler, StatementHandler)
   * @param interceptor
   * @return
   */
  public static Object wrap(Object target, Interceptor interceptor) {
    //如果方法是@Intercepts注解中指定的方法,则执行拦截逻辑
    Map, Set> signatureMap = getSignatureMap(interceptor);
    //CacheExecutor 对象对应的类
    Class<?> type = target.getClass();
    //获取类型对应的接口 org.apache.ibatis.executor.Executor
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    if (interfaces.length > 0) {
      // 4.2 使用jdk 创建executor等目标对象的代理对象
      return Proxy.newProxyInstance(
        type.getClassLoader(),
        interfaces,
        new Plugin(target, interceptor, signatureMap));
    }
    return target;
  }
mybatis3 源码深度解析-mybatis插件实现原理分析-jdk动态代理

当目标对象,执行完 pluginAll()方法返回的代理对象示例图

  • 当 pluginAll()方法返回的代理对象Executor 的某个方法被调用的时候,插件逻辑会先行执行。执行顺序由外而内,比如上图的执行顺序为 plugin3 → plugin2 → Plugin1 → Executor
  • 当 SqlSession执行 selectOne()方法时,经历过程如下

1 SqlSession执行 selectOne(),会委托给执行器执行

2 SqlSession会调用 Configuration.newExecutor() 工厂方法,获取到 Executor 动态代理对象后,

3 按照 jdk 动态代理机制,执行目标对象方法前会先执行 代理对象的 invoke() 方法,

4 而 plugin 中的 invoke()会调用拦截器对象的 intercept ()方法,执行拦截逻辑

5 执行完拦截器链的逻辑后,会调用目标对象方法,返回执行结果

mybatis3 源码深度解析-mybatis插件实现原理分析-jdk动态代理

插件逻辑执行过程


自定义分页分页插件

  • 默认情况下,MyBatis 允许使用插件来拦截的以下 4 个组件方法调用:

Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)

ParameterHandler (getParameterObject, setParameters)

ResultSetHandler (handleResultSets, handleOutputParameters)

StatementHandler (prepare, parameterize, batch, update, query)

//本示例对StatementHandler.prepare()方法进行拦截处理
public interface StatementHandler {
  Statement prepare(Connection connection, Integer transactionTimeout)
      throws SQLException;
}
/**
 * 自定义 mybatis 分页插件
 * @author ouzhx on 2022-07-24.
 */
//1 编写自定义插件
@Intercepts({// 使用这个注解,声明这是一个 mybaits拦截器
  //要拦截方法签名(type:被拦截方法所在的类,method:被拦截方法名,args:被拦截方法参数列表类型
  @Signature(type= StatementHandler.class,method = "parepare",args = {Connection.class,Integer.class})
})
public class PageInterceptor implements Interceptor {
  @Override
  public Object intercept(Invocation invocation) throws Throwable {
    //获取拦截的目标对象
    RoutingStatementHandler statementHandler = (RoutingStatementHandler) invocation.getTarget();
    BoundSql boundSql = statementHandler.getBoundSql();
    //获取请求入参参数对象
    Object parameterObject = boundSql.getParameterObject();

    /**
     * 1 分页逻辑处理参考, 定义一个 Page 对象
     * 2 如果入参parameterObject instanceof Page ,将入参对象转为  Page 对象
     * 3 查询总记录数
     * 4 生成分页 sql
     * 5 将BoundSql对象原始的 sql 语句替换为分页语句,没有 setSql()方法可用反射设置字段值
     *
     */
    return invocation.proceed();
  }

  @Override
  public Object plugin(Object target) {
    return Plugin.wrap(target, this);
  }

  @Override
  public void setProperties(Properties properties) {
     this.xxx=properties.getProperty("xxx");
  }
}



		



发表评论
留言与评论(共有 0 条评论) “”
   
验证码:

相关文章

推荐文章