Mybatis源码剖析之插件interceptor执行原理

简介: mybatis通过插件 对(Executor、StatementHandler、ParameterHandler、ResultSetHandler) 这四个 核心对象创建代理进行拦截 对mybatis来说插件就是拦截器,用来增强核心对象的功能,增强功能本质上是借助于底层的 动态代理实现的,换句话说,MyBatis中的四大对象都是代理对象

预读

mybatis通过插件 对(Executor、StatementHandler、ParameterHandler、ResultSetHandler) 这四个 核心对象创建代理进行拦截

对mybatis来说插件就是拦截器,用来增强核心对象的功能,增强功能本质上是借助于底层的 动态代理实现的,换句话说,MyBatis中的四大对象都是代理对象

Mybatis核心对象介绍

MyBatis的主要的核心部件有以下几个:

  • Configuration 初始化基础配置,比如MyBatis的别名等,一些重要的类型对象,如,插件,映射器,ObjectFactory和typeHandler对象,MyBatis所有的配置信息都维持在Configuration对象之中
  • SqlSessionFactory SqlSession工厂
  • SqlSession 作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能
  • Executor MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护
  • StatementHandler 封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数、将Statement结果集转换成List集合。
  • ParameterHandler 负责对用户传递的参数转换成JDBC Statement 所需要的参数,
  • ResultSetHandler 负责将JDBC返回的ResultSet结果集对象转换成List类型的集合;
  • TypeHandler 负责java数据类型和jdbc数据类型之间的映射和转换
  • MappedStatement MappedStatement维护了一条<select|update|delete|insert>节点的封装,
  • SqlSource 负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回
  • BoundSql 表示动态生成的SQL语句以及相应的参数信息

准备

/**
 * 自定义mybatis拦截器
 */
@Intercepts({
        // 拦截指定接口的指定方法
        @Signature(type= StatementHandler.class,method = "prepare",args = {Connection.class, Integer.class}),
//        @Signature(type = Executor.class,method = "query",args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class})
})
public class StatementInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        System.out.println("对方法进行增强....");
        // 执行原方法
        Object result = invocation.proceed();
        return result;
    }

    @Override
    public Object plugin(Object target) {
        // 调用插件,创建目标类的代理对象
        System.out.println("为 "+target+" 创建代理对象");
        return Plugin.wrap(target,this);
    }

    // 为拦截器设置参数值
    @Override
    public void setProperties(Properties properties) {
    }
}
<configuration>

    <!--加载外部的properties文件-->
    <properties resource="jdbc.properties"></properties>

    <!--开启全局的二级缓存配置-->
<!--    <settings>-->
<!--        <setting name="lazyLoadingEnabled" value="true"/>-->
<!--    </settings>-->

    <!--给实体类的全限定类名给别名-->
    <typeAliases>
        <!--给单独的实体起别名-->
        <!--  <typeAlias type="com.xiaoxuu.pojo.User" alias="user"></typeAlias>-->
        <!--批量起别名:该包下所有的类的本身的类名:别名还不区分大小写-->
        <package name="com.xiaoxu.pojo"/>
    </typeAliases>

    <plugins>
        <plugin interceptor="com.xiaoxu.interceptor.StatementInterceptor"></plugin>
    </plugins>


    <!--environments:运行环境-->
    <environments default="development">
        <environment id="development">
            <!--当前事务交由JDBC进行管理-->
            <transactionManager type="JDBC"></transactionManager>
            <!--当前使用mybatis提供的连接池-->
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>

    <!--引入映射配置文件-->
    <mappers>
        <!-- <mapper class="com.xiaoxuu.mapper.IOrderMapperr"></mapper>-->
        <package name="com.xiaoxu.mapper"/>
    </mappers>
</configuration>

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

interceptor执行原理剖析

1、mybatis在解析配置文件的时候,会创建拦截器,放入configuration中

/**
     * 解析 XML
     *
     * 具体 MyBatis 有哪些 XML 标签,参见 《XML 映射配置文件》http://www.mybatis.org/mybatis-3/zh/configuration.html
     *
     * @param root 根节点
     */
    private void parseConfiguration(XNode root) {
        try {
            //issue #117 read properties first
            // 解析 <properties /> 标签
            propertiesElement(root.evalNode("properties"));
            // 解析 <settings /> 标签
            Properties settings = settingsAsProperties(root.evalNode("settings"));
            // 加载自定义的 VFS 实现类
            loadCustomVfs(settings);
            // 解析 <typeAliases /> 标签
            typeAliasesElement(root.evalNode("typeAliases"));
            // 解析 <plugins /> 标签
            pluginElement(root.evalNode("plugins"));
            // 解析 <objectFactory /> 标签
            objectFactoryElement(root.evalNode("objectFactory"));
            // 解析 <objectWrapperFactory /> 标签
            objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
            // 解析 <reflectorFactory /> 标签
            reflectorFactoryElement(root.evalNode("reflectorFactory"));
            // 赋值 <settings /> 到 Configuration 属性
            settingsElement(settings);
            // read it after objectFactory and objectWrapperFactory issue #631
            // 解析 <environments /> 标签
            environmentsElement(root.evalNode("environments"));
            // 解析 <databaseIdProvider /> 标签
            databaseIdProviderElement(root.evalNode("databaseIdProvider"));
            // 解析 <typeHandlers /> 标签
            typeHandlerElement(root.evalNode("typeHandlers"));
            // 解析 <mappers /> 标签
            mapperElement(root.evalNode("mappers"));
        } catch (Exception e) {
            throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
        }
    }

在这里插入图片描述

在这里插入图片描述

Configuration中的interceptorChain

/**
 * 拦截器链
 *
 * @author Clinton Begin
 */
public class InterceptorChain {

    /**
     * 拦截器数组
     */
    private final List<Interceptor> interceptors = new ArrayList<>();

    /**
     * 应用所有插件
     *
     * @param target 目标对象
     * @return 应用结果
     */
    public Object pluginAll(Object target) {
        for (Interceptor interceptor : interceptors) {
            target = interceptor.plugin(target);
        }
        return target;
    }

    public void addInterceptor(Interceptor interceptor) {
        interceptors.add(interceptor);
    }

    public List<Interceptor> getInterceptors() {
        return Collections.unmodifiableList(interceptors);
    }

}

2、在创建sqlSession的时候,会创建executor,从Configuration中获取拦截器链,遍历拦截器链interceptorChain,对executor进行代理

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

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

同理 StatementHandler,paramenterHandler,ResultHandler也会通过拦截器链的方式创建代理对象,这些handler默认是需要的时候创建,而不是一开始就创建

// 创建 ParameterHandler 对象
    public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
        // 创建 ParameterHandler 对象
        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) {
        // 创建 DefaultResultSetHandler 对象
        ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
        // 应用插件
        resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
        return resultSetHandler;
    }

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

3、获取代理对象,代理对象调用方法,默认通过executor执行,默认先cacheExecutor,然后 baseExecutor

在这里插入图片描述

二级缓存所在的cacheExecutor
在这里插入图片描述

一级缓存所在的baseExecutor
在这里插入图片描述

4、通过executor执行,默认会创建statmentHandler处理sql编译,参数设置,结果处理‘

在这里插入图片描述

预编译sql,由于是代理对象statementHandler,那么走invoke

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

由于我们指定了要对StatementHandler的prepare方法进行拦截调用,所以此时invoke方法中会判断是否 对该方法进行拦截。 如果拦截的方法是目标方法,那么走拦截器

在这里插入图片描述

拦截器增强方法

在这里插入图片描述

走完增强方法,走原方法

/**
 * 方法调用信息
 *
 * @author Clinton Begin
 */
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 getTarget() {
        return target;
    }

    public Method getMethod() {
        return method;
    }

    public Object[] getArgs() {
        return args;
    }

    /**
     * 调用方法
     *
     * @return 调用结果
     */
    public Object proceed() throws InvocationTargetException, IllegalAccessException {
        return method.invoke(target, args);
    }

}

总结

在这里插入图片描述

相关文章
|
2月前
|
SQL XML Java
一文搞懂Mybatis执行原理
一文搞懂Mybatis执行原理
40 1
|
2月前
|
Java 数据库连接 Maven
使用mybatis插件generator生成实体类,dao层和mapper映射
使用mybatis插件generator生成实体类,dao层和mapper映射
49 0
|
17天前
|
SQL Java 数据库连接
【mybatis】第一篇,Springboot中使用插件PageHelper不生效解决方案
【mybatis】第一篇,Springboot中使用插件PageHelper不生效解决方案
|
1月前
|
Java 数据库连接 mybatis
mybatis简单案例源码详细【注释全面】——实体层(User.java)
mybatis简单案例源码详细【注释全面】——实体层(User.java)
13 0
|
1天前
|
SQL XML Java
Mybatis-Plus插件扩展MybatisX
Mybatis-Plus插件扩展MybatisX
10 0
|
13天前
|
SQL Java 数据库连接
深入源码:解密MyBatis数据源设计的精妙机制
深入源码:解密MyBatis数据源设计的精妙机制
27 1
深入源码:解密MyBatis数据源设计的精妙机制
|
1月前
|
Java 数据库连接 mybatis
mybatis简单案例源码详细【注释全面】——Utils层(MybatisUtils.java)
mybatis简单案例源码详细【注释全面】——Utils层(MybatisUtils.java)
13 0
|
1月前
|
Java 数据库连接 mybatis
mybatis简单案例源码详细【注释全面】——测试层(UserMapperTest.java)
mybatis简单案例源码详细【注释全面】——测试层(UserMapperTest.java)
9 0
|
1月前
|
Java 数据库连接 mybatis
mybatis简单案例源码详细【注释全面】——Dao层映射文件(UserMapper.xml)【重要】
mybatis简单案例源码详细【注释全面】——Dao层映射文件(UserMapper.xml)【重要】
10 0