MyBatis 插件之拦截器Interceptor源码解析(五)

本文深入探讨MyBatis拦截器Interceptor的原理与实现,介绍了Interceptor如何拦截Executor、StatementHandler等核心组件,以及如何在拦截器中自定义逻辑,如SQL动态修改、性能监控。通过MetaObject和Plugin工具类,理解MyBatis的动态代理机制,了解创建自定义插件的步骤和配置。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

MyBatis 插件之拦截器(Interceptor)

一.背景

​ 拦截器的一个作用就是我们可以拦截某些方法的调用,我们可以选择在这些被拦截的方法执行前后加上某些逻辑,也可以在执行这些被拦截的方法时执行自己的逻辑而不再执行被拦截的方法。Mybatis拦截器设计的一个初衷就是为了供用户在某些时候可以实现自己的逻辑而不必去动Mybatis固有的逻辑。打个比方,对于Executor,Mybatis中有几种实现:BatchExecutor、ReuseExecutor、SimpleExecutor和CachingExecutor。这个时候如果你觉得这几种实现对于Executor接口的query方法都不能满足你的要求,那怎么办呢?是要去改源码吗?当然不。我们可以建立一个Mybatis拦截器用于拦截Executor接口的query方法,在拦截之后实现自己的query方法逻辑,之后可以选择是否继续执行原来的query方法。

​ 在很多业务场景下我们需要去拦截sql,达到不入侵原有代码业务处理一些东西,比如:分页操作,数据权限过滤操作,SQL执行时间性能监控、在进行多租户开发时,数据要按租户隔离,可以在sql语句后面统一添加租户编号筛选条件等等,这里我们就可以用到Mybatis的拦截器Interceptor

二.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执行概要图

img

四.MyBatis 拦截器原理实现

Mybatis支持对Executor、StatementHandler、PameterHandler和ResultSetHandler 接口进行拦截,也就是说会对这4种对象进行代理
首先从配置文件解析开始
​ 通过SqlSessionFactoryBean去构建Configuration添加拦截器并构建获取SqlSessionFactory

/**
 * {@code FactoryBean} that creates an MyBatis {@code SqlSessionFactory}.
 * This is the usual way to set up a shared MyBatis {@code SqlSessionFactory} in a Spring application context;
 * the SqlSessionFactory can then be passed to MyBatis-based DAOs via dependency injection.
 *
 * Either {@code DataSourceTransactionManager} or {@code JtaTransactionManager} can be used for transaction
 * demarcation in combination with a {@code SqlSessionFactory}. JTA should be used for transactions
 * which span multiple databases or when container managed transactions (CMT) are being used.
 *
 * @author Putthibong Boonbong
 * @author Hunter Presnall
 * @author Eduardo Macarron
 * @author Eddú Meléndez
 * @author Kazuki Shimizu
 *
 * @see #setConfigLocation
 * @see #setDataSource
 */
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
   
 
    private static final Log LOGGER = LogFactory.getLog(SqlSessionFactoryBean.class);
 
    private Resource configLocation;
 
    private Configuration configuration;
 
    private Resource[] mapperLocations;
 
    private DataSource dataSource;
 
    private TransactionFactory transactionFactory;
 
    private Properties configurationProperties;
 
    private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
 
    // ... 此处省略部分源码
 
    /**
     * Build a {@code SqlSessionFactory} instance.
     *
     * The default implementation uses the standard MyBatis {@code XMLConfigBuilder} API to build a
     * {@code SqlSessionFactory} instance based on an Reader.
     * Since 1.3.0, it can be specified a {@link Configuration} instance directly(without config file).
     *
     * @return SqlSessionFactory
     * @throws IOException if loading the config file failed
     */
    protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
   
 
 
        Configuration configuration;
        // 根据配置信息构建Configuration实体类
        XMLConfigBuilder xmlConfigBuilder = null;
        if (this.configuration != null) {
   
            configuration = this.configuration;
            if (configuration.getVariables() == null) {
   
                configuration.setVariables(this.configurationProperties);
            } else if (this.configurationProperties != null) {
   
                configuration.getVariables().putAll(this.configurationProperties);
            }
        } else if (this.configLocation != null) {
   
            xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
            configuration = xmlConfigBuilder.getConfiguration();
        } else {
   
            if (LOGGER.isDebugEnabled()) {
   
                LOGGER.debug("Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
            }
            configuration = new Configuration();
            if (this.configurationProperties != null) {
   
                configuration.setVariables(this.configurationProperties);
            }
        }
 
        // ... 此处省略部分源码
 
        if (hasLength(this.typeAliasesPackage)) {
   
            String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,
                    ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
            for (String packageToScan : typeAliasPackageArray) {
   
                configuration.getTypeAliasRegistry().registerAliases(packageToScan,
                        typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);
                if (LOGGER.isDebugEnabled()) {
   
                    LOGGER.debug("Scanned package: '" + packageToScan + "' for aliases");
                }
            }
        }
 
        if (!isEmpty(this.typeAliases)) {
   
            for (Class<?> typeAlias : this.typeAliases) {
   
                configuration.getTypeAliasRegistry().registerAlias(typeAlias);
                if (LOGGER.isDebugEnabled()) {
   
                    LOGGER.debug("Registered type alias: '" + typeAlias + "'");
                }
            }
        }
        // 查看是否注入拦截器,有的话添加到Interceptor集合里面
        if (!isEmpty(this.plugins)) {
   
            for (Interceptor plugin : this.plugins) {
   
                configuration.addInterceptor(plugin);
                if (LOGGER.isDebugEnabled()) {
   
                    LOGGER.debug("Registered plugin: '" + plugin + "'");
                }
            }
        }
 
        // ... 此处省略部分源码
 
        return this.sqlSessionFactoryBuilder.build(configuration);
    }
 
    // ... 此处省略部分源码
}

通过原始的XMLConfigBuilder 构建configuration添加拦截器

public class XMLConfigBuilder extends BaseBuilder {
   
    //解析配置
    private void parseConfiguration(XNode root) {
   
        try {
   
            //省略部分代码
            pluginElement(root.evalNode("plugins"));
 
        } catch (Exception e) {
   
            throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
        }
    }
 
    private void pluginElement(XNode parent) throws Exception {
   
        if (parent != null) {
   
            for (XNode child : parent.getChildren()) {
   
                String interceptor = child.getStringAttribute("interceptor");
                Properties properties = child.getChildrenAsProperties();
                Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
                interceptorInstance.setProperties(properties);
                //调用InterceptorChain.addInterceptor
                configuration.addInterceptor(interceptorInstance);
            }
        }
    }
}

上面是两种

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值