当前位置: 首页 > news >正文

Mybatis源码 插件机制

简介

插件是一种常见的扩展方式,大多数开源框架也都支持用户通过添加自定义插件的方式来扩展或者改变原有的功能,MyBatis中也提供的有插件,虽然叫插件,但是实际上是通过拦截器(Interceptor)实现的,在MyBatis的插件模块中涉及到责任链模式和JDK动态代理。

如何自定义实现插件?

step1:实现Interceptor接口;

step2:配置拦截器在全局配置文件

<plugins>
       <plugin interceptor="com.dura.interceptor.FirstInterceptor">
       <-- 这里的这个属性,我没写哈;不过,这里的用法倒是挺重要的,定义在拦截器中属性  -->
         <property name="testProp" value="1000"/>
       </plugin>
    </plugins>

step3:运行。

插件实现的原理?

初始化操作——全局配置文件解析

该方法用来解析全局配置文件中的plugins标签,然后对应的创建Interceptor对象,并且封装对应的属性信息。最后调用了Configuration对象中的方法。 configuration.addInterceptor(interceptorInstance)

通过这个代码我们发现我们自定义的拦截器最终是保存在了InterceptorChain这个对象中。而InterceptorChain的定义为

创建代理对象过程?

在解析的时候创建了对应的Interceptor对象,并保存在了InterceptorChain中,那么这个拦截器是如何和对应的目标对象进行关联的呢

首先拦截器可以拦截的对象是Executor,ParameterHandler,ResultSetHandler,StatementHandler。

那么我们来看下这四个对象在创建的时候又什么要注意的

Executor

Executor在装饰完二级缓存后会通过pluginAll来创建Executor的代理对象。

StatementHandler

  @Override
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      // 注意,已经来到SQL处理的关键对象 StatementHandler
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      // 获取一个 Statement对象
      stmt = prepareStatement(handler, ms.getStatementLog());
      // 执行查询
      return handler.query(stmt, resultHandler);
    } finally {
      // 用完就关闭
      closeStatement(stmt);
    }
  }
  // 进入创建的方法
 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);
    // 植入插件逻辑(返回代理对象)
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }
 //  可以看到statementHandler的代理对象

ParameterHandler

  public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    // StatementType 是怎么来的? 增删改查标签中的 statementType="PREPARED",默认值 PREPARED
    switch (ms.getStatementType()) {
      case STATEMENT:
        delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case PREPARED:
        // 创建 StatementHandler 的时候做了什么? >>
        delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case CALLABLE:
        delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      default:
        throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
    }

  }

// 进入某个Handler看下  
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
    // 植入插件逻辑(返回代理对象)
    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
    return parameterHandler;
  }

ResultSetHandler

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

执行过程

以Executor的query方法为例,当查询请求到来的时候,Executor的代理对象是如何处理拦截请求的呢?我们来看下。当请求到了executor.query方法的时候


// 执行Plugin的invoke 方法
/**
   * 代理对象方法被调用时执行的代码
   * @param proxy
   * @param method
   * @param args
   * @return
   * @throws Throwable
   */
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      // 获取当前方法所在类或接口中,可被当前Interceptor拦截的方法
      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);
    }
  }
  //然后进入interceptor.intercept 会进入我们自定义的 FirstInterceptor对象中
      /**
     * 执行拦截逻辑的方法
     * @param invocation
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        System.out.println("FirtInterceptor  拦截之前 ....");
        Object obj = invocation.proceed(); // 执行目标方法
        System.out.println("FirtInterceptor  拦截之后 ....");
        return obj;
    }

以上就是自定义的拦截器执行的完整流程。

如果我们有多个拦截器怎么办

如果我们有多个自定义的拦截器,那么他的执行流程是怎么样的呢?比如我们创建了两个 Interceptor 都是用来拦截 Executor 的query方法,一个是用来执行逻辑A 一个是用来执行逻辑B的。

总结:Interceptor的相关对象作用

对象作用
Interceptor自定义插件需要实现接口,实现4个方法
InterceptChain配置的插件解析后会保存在Configuration的InterceptChain中
Plugin触发管理类,还可以用来创建代理对象
Invocation对被代理类进行包装,可以调用proceed()调用到被拦截的方法

应用场景分析——其实在说拦截器啦

作用描述实现方式
水平分表一张费用表按月度拆分为12张表。fee_202001-202012。当查询条件出现月度(tran_month)时,把select语句中的逻辑表名修改为对应的月份表。对query update方法进行拦截在接口上添加注解,通过反射获取接口注解,根据注解上配置的参数进行分表,修改原SQL,例如id取模,按月分表
数据脱敏手机号和身份证在数据库完整存储。但是返回给用户,屏蔽手机号的中间四位。屏蔽身份证号中的出生日期。query——对结果集脱敏
菜单权限控制不同的用户登录,查询菜单权限表时获得不同的结果,在前端展示不同的菜单对query方法进行拦截在方法上添加注解,根据权限配置,以及用户登录信息,在SQL上加上权限过滤条件
黑白名单有些SQL语句在生产环境中是不允许执行的,比如like %%对Executor的update和query方法进行拦截,将拦截的SQL语句和黑白名单 进行比较,控制SQL语句的执行
全局唯一ID在高并发的环境下传统的生成ID的方式不太适用,这时我们就需要考虑其他方式了创建插件拦截Executor的insert方法,通过UUID或者雪花算法来生成ID,并修改SQL中的插入信息

http://www.dtcms.com/a/99153.html

相关文章:

  • Vue3 项目通过 docxtemplater 插件动态渲染 .docx 文档(带图片)预览,并导出
  • 人工智能与软件工程结合的发展趋势
  • 一些常用开发软件下载地址
  • [Python]如何利用Flask搭建一個Web服務器,並透過Ngrok訪問來實現LINE Bot功能?
  • MySQL数据库的操作(mybatis)
  • Spring学习笔记06——bean、java bean、spring bean、POJO几个概念讲解
  • 算法刷题记录——LeetCode篇(1.2) [第11~20题](持续更新)
  • Labview学习记录
  • 杂草YOLO系列数据集4000张
  • 【MySQL基础-16】MySQL DELETE语句:深入理解与应用实践
  • Ray AI - 概述、安装、入门
  • 【HTML 基础教程】HTML <head>
  • Java多线程与高并发专题——Condition 和 wait/notify的关系
  • python:模块
  • app整改报告怎么写?app整改方案分享
  • 液压式精密矫平机——精准掌控,重塑金属平整新高度
  • 【黑马点评】Redis解决集群的session共享问题
  • wait函数等待多个子进程
  • vue3对比vue2新增特性
  • CSS 边框(Border)样式详解
  • 泛目录优化:无极泛目录优化网站,技术解析与风险控制指南
  • Flutter开发There are multiple heroes that share the same tag within a subtree报错
  • C++ explicit
  • 使用Java操作Redis
  • 在 Windows 中查看 Nginx 当前占用的端口
  • 基于高德地图实现地图交互功能的探索与总结
  • 函数式组件中的渲染函数 JSX
  • Python基础教程:从格式化到项目管理
  • QT操作PDF文件
  • 计算机视觉准备八股中