Mybatis插件原理分析及自定义一个插件

Mybatis作为一个优秀的ORM持久层框架,其中一个特点就是具有很强大的灵活性,为开发人员提供了简单易用的插件扩展机制。Mubatis支持对其四大组件Executor(执行器)、StatementHandler(SQL语法构建器)、ParamaterHandler(参数处理器)、ResultSetHandler(结果集处理器)进行拦截,实现增强功能(Mybatis中四大对象都是代理对象)。

本文主要内容

1.自定义一个插件;

2.从源码角度剖析Mybatis插件原理;

1.自定义插件

A.编写自定义插件类MyPlugin实现org.apache.ibatis.plugin.Interceptor并实现相关方法(intercept、plugin、setProperties)

在方法上增加公式

@Interceptors{

   @Signature( // Signature可配置多个,一个拦截器可以对多个类方法进行拦截
            type = ResultSetHandler.class, // 要增强的类
             method = "handleResultSets", // 要增强的方法名
            args = {Statement.class}),// 方法传入参数类型,防止因为方法重载,找不到具体的方法

}

package com.kay.plugin;


import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.util.Properties;

/**
 * @Description: 自定义插件   ------ 步骤1 实现Interceptor接口-----
 * @Author: Kay
 */
@Intercepts({
// 可配置多个 @Signature
//        @Signature(
//                type = ResultSetHandler.class,
//                method = "handleResultSets",
//                args = {Statement.class}),// 参数类型,防止因为方法重载,找不到具体的方法
        @Signature(
                type = Executor.class,
                method = "query",
                args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}) // MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler
        
})
public class MyPlugin implements Interceptor {
    /**
     * 5. 在代理类执行方法时,会触发invoke(),如果该方法在signatureMap(@Signature)中,将触发此处的 intercept() 方法
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 此处的invocation 在Plugin.invoke方法中,  传入的 new Invocation(target, method, args)
        System.out.println("---------------------------------------  方法已经被增强了,即将要执行原方法");
        return invocation.proceed(); // 原方法继续执行
    }

    /**
     * 1.当 MyPlugin 在 SqlMapConfig.xml中配置在plugin标签中;
     * 2.MyBatis在初始化阶段,XMLConfigBuilder通过加载SqlMapConfig时,会将当前这个过滤器加入到 InterceptorChain中的List interceptors链路中
     * 3.初始化四大对象的时候,会调用pluginAll(); 调用interceptor.plugin()进入到本方法
     * 4.在本方法中调用Plugin.wrap对为了对目标类(四大对象)创建代理对象
     */
    @Override
    public Object plugin(Object target) {
        System.out.println("-------------------------将要对" + target + "进行增强 ------------------------");
        return Plugin.wrap(target, this);// 这一步是为了将目标对象进行包装增强
    }

    @Override
    public void setProperties(Properties properties) {
        System.out.println("-------------------------插件配置的属性值" + properties);
    }
}

B.在SqlMapConfig.xml标签中引入当前MyPlugin类

    
        
    

(ps:注意标签在中的顺序问题)

C.测试及结果 (在Mybatis初始化及创建Executor对象过程中,执行拦截器plugin方法生成Executor的代理对象),在执行对象的特定方法时,会调用interceptor方法

    @Test
    public void testSelect() throws IOException {
        InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
        SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = sessionFactory.openSession();
        System.out.println("-------- sqlSession已创建,准备执行 查询方法 ");
        User user = sqlSession.selectOne("com.kay.dao.UserMapper.findById",1);
    }

Mybatis插件原理分析及自定义一个插件_第1张图片

2.从源码角度剖析Mybatis插件原理(以ResultSetHandler为例 以上案例测试使用的Executor,原理都一样)

Mybatis插件原理分析及自定义一个插件_第2张图片

A.当 MyPlugin 在 SqlMapConfig.xml中配置在plugin标签中;MyBatis在初始化阶段,XMLConfigBuilder通过加载SqlMapConfig时,会将当前这个过滤器加入到 InterceptorChain中的List interceptors链路中(此截图为简化后的代码图,更多Mybatis整体架构源码跟踪,可见MyBatis源码剖析章节)

Mybatis插件原理分析及自定义一个插件_第3张图片

B.在初始化ResultSetHandler过程中会调用InterceptorChain.pluginAll()方法

Mybatis插件原理分析及自定义一个插件_第4张图片

C.在pluginAll()会循环interceptors中所有过滤器,调用过滤器定义的interceptor.plugin()方法,(即MyPlugin中的plugin方法)在自定义拦截器的 plugin 方法中,调用Plugin.wrap(target, this)

Mybatis插件原理分析及自定义一个插件_第5张图片

D.Plugin类实现了 InvocationHandler 接口,在wrap方法中根据过滤器配置了对象(此案例中为ResultSetHandler)中的方法(即MyPlugin案例中@Interceptors中配置了ResultSetHandler中的handleResultSets方法)生成代理后的对象

Mybatis插件原理分析及自定义一个插件_第6张图片

====》》》 在执行对象ResultSetHandler 的 handleResultSets方法时,会进入到Plugin中的invoke方法,在invoke方法判断当前对象ResultSetHandler 是否被拦截,如果被拦截,就会进入 执行相应拦截器(MyPlugin)的intercept()方法

总结:

Mybaits自定义插件本质上是拦截器,底层通过动态代理生成四大核心对象Executor、StatementHandler、ParamaterHandler、ResultSetHandler的代理对象;

在MyBatis所有类中,只有 这四大核心对象在初始化的时候调用了InterceptorChain.pluginAll方法

相关代码:https://gitee.com/sunshinekay/mybatis-practice.git

你可能感兴趣的:(Mybatis系列,mybatis,java,动态代理,源码)