[Professor麦]浅谈Mybatis插件机制

哈哈哈,今天又拖更了!!

Mybatis插件机制

废话不多说,直接进入主题,这个Mybatis插件机制有点像Spring MVC的拦截器,底层的設計模式都是拦截器模式。

插件机制原理

我们在编写插件时,除了需要让插件类实现 Interceptor 接口,还需要通过注解标注该插件的拦截点。所谓拦截点指的是插件所能拦截的方法,MyBatis 所允许拦截的方法如下:

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

如果我们想要拦截 Executor 的 query 方法,那么可以这样定义插件。

@Intercepts({
    @Signature(
        type = Executor.class,
        method = "query",
        args ={MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
    )
})

除此之外,我们还需将插件配置到相关文件中。这样 MyBatis 在启动时可以加载插件,并保存插件实例到相关对象(InterceptorChain,拦截器链)中。待准备工作做完后,MyBatis 处于就绪状态。我们在执行 SQL 时,需要先通过 DefaultSqlSessionFactory 创建 SqlSession 。Executor 实例会在创建 SqlSession 的过程中被创建,Executor 实例创建完毕后,MyBatis 会通过 JDK 动态代理为实例生成代理类。这样,插件逻辑即可在 Executor 相关方法被调用前执行。

以上就是 MyBatis 插件机制的基本原理。

植入插件逻辑

从源头开始:

// DefaultSqlSessionFactory
1.
public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  }
2. 
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
	// 省略
      // 创建 Executor
    final Executor executor = configuration.newExecutor(tx, execType);
    return new DefaultSqlSession(configuration, executor, autoCommit);
  } catch (Exception e) {
    closeTransaction(tx); // may have fetched a connection so lets call close()
    throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
  } finally {
    ErrorContext.instance().reset();
  }
}

3. 
// -☆- Configuration
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    
    // 根据 executorType 创建相应的 Executor 实例
    if (ExecutorType.BATCH == executorType) {...} 
    else if (ExecutorType.REUSE == executorType) {...} 
    else {
        executor = new SimpleExecutor(this, transaction);
    }
    if (cacheEnabled) {
        executor = new CachingExecutor(executor);
    }
    
    // 植入插件
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
}

// newExecutor 方法在创建好 Executor 实例后,紧接着通过拦截器链 interceptorChain 为 Executor 实例植入代理逻辑。
public class InterceptorChain {

    private final List<Interceptor> interceptors = new ArrayList<Interceptor>();

    public Object pluginAll(Object target) {
        // 遍历拦截器集合
        for (Interceptor interceptor : interceptors) {
            // 调用拦截器的 plugin 方法植入相应的插件逻辑
            target = interceptor.plugin(target);
        }
        return target;
    }
    
    /** 添加插件实例到 interceptors 集合中 */
    public void addInterceptor(Interceptor interceptor) {
        interceptors.add(interceptor);
    }

    /** 获取插件列表 */
    public List<Interceptor> getInterceptors() {
        return Collections.unmodifiableList(interceptors);
    }
}

它的 pluginAll 方法会调用具体插件的 plugin 方法植入相应的插件逻辑。如果有多个插件,则会多次调用 plugin 方法,最终生成一个层层嵌套的代理类。img

当 Executor 的某个方法被调用的时候,插件逻辑会先行执行。执行顺序由外而内,比如上图的执行顺序为 plugin3 → plugin2 → Plugin1 → Executor

// 这个类SqlCostTimeInterceptor是我自定义实现的,稍后会说
// -☆- SqlCostTimeInterceptor
public Object plugin(Object target) {
    return Plugin.wrap(target, this);
}

// -☆- Plugin
public static Object wrap(Object target, Interceptor interceptor) {
    /*
     * 获取插件类 @Signature 注解内容,并生成相应的映射结构。形如下面:
     * {
     *     Executor.class : [query, update, commit],
     *     ParameterHandler.class : [getParameterObject, setParameters]
     * }
     */
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    Class<?> type = target.getClass();
    // 获取目标类实现的接口
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    if (interfaces.length > 0) {
        // 通过 JDK 动态代理为目标类生成代理类
        // Plugin 类实现了 InvocationHandler 接口,因此它可以作为参数传给 Proxy 的 newProxyInstance 方法
        return Proxy.newProxyInstance(
            type.getClassLoader(),
            interfaces,
            new Plugin(target, interceptor, signatureMap));
    }
    return target;
}

  1. 可以看看signatureMap是什么:image-20200717171251815

  2. 再看看我自定义类上的@Signature注解:

    @Intercepts({
            @Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}),
            @Signature(type = StatementHandler.class, method = "update", args = {Statement.class}),
            @Signature(type = StatementHandler.class, method = "batch", args = {Statement.class})})
    

很明显type对应着Map中的keymethod对应着value

执行插件逻辑

植入完之后,就要执行插件逻辑了!Plugin 实现了 InvocationHandler 接口,因此它的 invoke 方法会拦截所有的方法调用invoke 方法会对所拦截的方法进行检测,以决定是否执行插件逻辑

// -☆- Plugin
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
        /*
         * 获取被拦截方法列表,比如:
         *    signatureMap.get(Executor.class),可能返回 [query, update, commit]
         */
        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);
    }
}

// 插件逻辑封装在 intercept 中,该方法的参数类型为 Invocation。Invocation 主要用于存储目标类,方法以及方法参数列表。下面简单看一下该类的定义
public class Invocation {

    private final Object target;
    private final Method method;
    private final Object[] args;

    public Invocation(Object target, Method method, Object[] args) {
        this.target = target;
        this.method = method;
        this.args = args;
    }

    // 省略部分代码

    public Object proceed() throws InvocationTargetException, IllegalAccessException {
        // 调用被拦截的方法
        return method.invoke(target, args);
    }
}

好了,插件逻辑就是这样子,就是JDK动态代理!!(迟点再出一篇动态代理的文章)

自定义实现Mybatis插件

// 先定义一个拦截器
@Intercepts({
        @Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}),
        @Signature(type = StatementHandler.class, method = "update", args = {Statement.class}),
        @Signature(type = StatementHandler.class, method = "batch", args = {Statement.class})})
public class SqlCostTimeInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object target = invocation.getTarget();
        long startTime = System.currentTimeMillis();
        StatementHandler statementHandler = (StatementHandler) target;
        try {
            return invocation.proceed();
        } finally {
            long costTime = System.currentTimeMillis() - startTime;
            BoundSql boundSql = statementHandler.getBoundSql();
            String sql = boundSql.getSql();
            System.out.println("执行 SQL:" + sql + "执行耗时:" + costTime + "ms");
        }
    }

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


    @Override
    public void setProperties(Properties properties) {
        System.out.println("插件配置的信息:"+properties);
    }
}


配置信息:

<plugins>
    <plugin interceptor="Interceptor.MySqlPagingPlugin"></plugin>
</plugins>

直接就可以使用了!!image-20200717191703984

个人唠叨

​ 说一下算法的进展吧,额,进展得不错,今晚也顺利地刷了算法,哈哈哈,2个小时刷了2道题都是media(作为算法小白的我,已经很满足了,要加油啊!!!),接下来就打算复盘一下之前学过的基础知识,但是也没那么好进行复盘了,感觉这个基础知识是最难搞的,平时做项目可能又用不到,有些知识点又被问得比较频繁,接下来这段时间,基础知识的复盘可能会以一种面试题的形式进行吧。不过最近也在搞Tomcat源码,这里面也包含了很多基础知识,还是老师那句话。结合项目更容易理解,今天看Tomcat源码,也发现了线程池的应用和设计模式的应用,感觉比起以前单纯看知识点更容易理解了!!image-20200720221957534

谈谈我大学遇到的那个她(这里先说结论,我们最后是没有在一起的)下篇,建议先看看上篇

先从近开始说起吧!!image-20200720222507619

我们最后一次说话,时间应该定格在11月21号,那天是他的生日,其实在这之前我已经表白了,不过失败了,他说,我们还可以做朋友!(我缺的是朋友吗?hhh,我缺的是一个懂我理解我的女朋友)

为什么会一直拖到她生日那天呢,主要是我之前生日,他给我送了个蛋糕,还在操场等我下课一起吃蛋糕,所以,我觉得要礼尚往来吧,所以怎么样也要准备一份生日礼物吧,我那时候很喜欢拍视频,所以就约她提前一周出来玩,我就利用这一周搞个视频出来,感兴趣的朋友可以看看,不过也不是什么大神制作(不好看勿喷,纯业余)!这也是我拍的最后一个视频,从那以后,我好像也好久没拿起手机拍视频,拍照片了!

好了,说说重点吧!!哈哈哈image-20200720224025940

如上图,我现在还不明白什么是作为好朋友的喜欢,总之一句话吧,他就是不喜欢我,哈哈哈,第一次表白,还是以视频的方式表白了,感觉自己已经做好充分的准备了,也没遗憾了!!

好了,就说到这吧!最近有朋友问我,半年都没见我一条朋友圈,不是因为我懒,因为朋友圈天太多认识的人啦,很多真实的感受都不能表达出来,所以就换个平台来释放自己的表达欲!

时间也差不多了,各位老铁,晚安!!!

提醒:我每篇文章后面都会有个人唠叨,都会分享自己的一些生活经验等等,希望自己能做一个不至于技术的博主,加油哟!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值