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

预读

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

}

总结

在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

白鸽呀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值