构建自己的拦截器:深入理解MyBatis的拦截机制

您所在的位置:网站首页 shardingjdbc和mybatis拦截器的顺序 构建自己的拦截器:深入理解MyBatis的拦截机制

构建自己的拦截器:深入理解MyBatis的拦截机制

2024-07-15 01:09:31| 来源: 网络整理| 查看: 265

Mybatis拦截器系列文章:从零开始的 MyBatis 拦截器之旅:实战经验分享 构建自己的拦截器:深入理解MyBatis的拦截机制 Mybatis分页插件之PageHelper原理解析

文章目录 前言拦截器声明注册-解析-添加拦截器注册拦截器解析-添加拦截器 拦截器执行及原理--如何起作用的为什么只能对4种组件增强?(如何生成代理对象的?)pluginAll获取所有要增强的方法创建Plugin对象 代理对象是如何执行的拦截器代理对象链路调用 小结:

前言

在这里插入图片描述

Mybatis拦截器并不是每个对象里面的方法都可以被拦截的。Mybatis拦截器只能拦截Executor、StatementHandler、ParameterHandler、ResultSetHandler四个类里面的方法,这四个对象在创建的时候才会创建代理。

Mybatis拦截器是Mybatis提供的一种插件功能,它允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,Mybatis允许使用插件来拦截的方法调用包括:Executor、ParameterHandler、ResultSetHandler和StatementHandler。这些方法调用是Mybatis执行过程中的关键点,因此,拦截器可以在这些关键点上进行拦截,实现自定义的功能。

用途:实际工作中,可以使用Mybatis拦截器来做一些SQL权限校验、数据过滤、数据加密脱敏、SQL执行时间性能监控和告警等。最常见就是我们的分页插件PageHelper

基础讲解可以参考我之前的文章:从零开始的 MyBatis 拦截器之旅:实战经验分享

接下来我将从以下3个层面一步步讲解mybatis的拦截机制:

拦截器声明--->注册-解析-添加拦截器--->拦截器执行及原理(如何起作用的)

当然这里最重要的肯定是最后一步!

拦截器声明

在讲解拦截器执行原理之前,我们先简单看一个拦截器的例子:我们这是一个拦截器mybatis执行SQL慢查询的拦截器

要使用拦截器,那我们肯定要声明写一个我们自己需要的拦截器,步骤很简单:

自定义拦截器 实现 org.apache.ibatis.plugin.Interceptor 接口与其中的方法。在plugin方法中需要返回 return Plugin.wrap(o, this)。在intercept方法中可以实现拦截的业务逻辑,改方法的 参数 Invocation中有原始调用的 对象,方法和参数,可以对其任意处理。在自定义的拦截器上添加需要拦截的对象和方法,通过注解 @Intercepts(org.apache.ibatis.plugin.Intercepts) 添加。如示例代码所示:

Intercepts的值是一个签名数组,签名中包含要拦截的 类,方法和参数。

@Intercepts({ @Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}), @Signature(type = StatementHandler.class, method = "update", args = {Statement.class}) }) public class PerformanceInterceptor implements Interceptor { private long maxTolerate; @Override public Object intercept(Invocation invocation) throws Throwable { System.out.println("PerformanceInterceptor intercept run ......"); long startTime = System.currentTimeMillis(); //执行原有目前原始对象的方法 Object retVal = invocation.proceed(); long endTime = System.currentTimeMillis(); //判断如果超过了某个时间,则是慢SQL,进行对应处理 if (endTime - startTime > maxTolerate) { //...... } return retVal; } @Override public void setProperties(Properties properties) { this.maxTolerate = Long.parseLong(properties.getProperty("maxTolerate")); } }

这个拦截器表示要代理的对象是StatementHandler 类型的,要代理的方法是query和update方法。也就是只有执行StatementHandler的query和update方法才会执行拦截器的拦截策略 :也就是我们上面的PerformanceInterceptor类的intercept方法

注册-解析-添加拦截器

声明完了拦截器以后,就要对我们的拦截器进行注册/配置,然后对配置进行解析添加

注册拦截器

xml注册是最基本的方式,是通过在Mybatis配置文件中plugins元素来进行注册的。一个plugin对应着一个拦截器,在plugin元素可以指定property子元素,在注册定义拦截器时把对应拦截器的所有property通过Interceptor的setProperties方法注入给拦截器。因此拦截器注册xml方式如下:

解析-添加拦截器

注册好拦截器以后,接着就是要对我们的拦截器进行解析添加了,如下所示:

如果是原生的mybatis,则在XMLConfigBuilder#pluginElement会进行解析,而pluginElement的调用则是在new SqlSessionFactoryBuilder().build(xml) 具体这部分的内容可以参考我之前的文章 !超硬核解析Mybatis动态代理原理!只有接口没实现也能跑?

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).getDeclaredConstructor().newInstance(); interceptorInstance.setProperties(properties); configuration.addInterceptor(interceptorInstance); } } }

如果在Spring boot中使用,则需要单独写一个配置类,如下sqlSessionFactory.getConfiguration().addInterceptor(customInterceptor) 就是类似上面configuration.addInterceptor(interceptorInstance);的注册效果

@Configuration public class MybatisInterceptorConfig { @Bean public String performanceInterceptor(SqlSessionFactory sqlSessionFactory) { PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor(); Properties properties = new Properties(); properties.setProperty("maxTolerate","10"); performanceInterceptor.setProperties(properties); sqlSessionFactory.getConfiguration().addInterceptor(customInterceptor); return "performanceInterceptor"; } } 拦截器执行及原理–如何起作用的

上面两步已经把我们要的拦截器声明并注册添加到好了,紧接着就是要对这个拦截器进行真正使用了,这里是重点,我们看看它是如何起作用的!

b15c8d8da7c8dd68e485bc6c5e16c225.png 为什么只能对4种组件增强?(如何生成代理对象的?)

为什么只能对4种组件增强? 换个说法,也就是我们如何生成代理对象的,这两个问题的答案其实是一样的 所以有时候我们面试时也是类似,同一个问题,面试官换个问法,就不懂怎么回答了,当然重点还是要真正理解了,而不是死记硬背

MyBatis 的插件可以对四种组件进行增强:但是为什么呢??

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

重点就是interceptorChain.pluginAll: 下面4个方法实例化了对应的对象之后,都会调用interceptorChain的pluginAll方法

public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) { ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql); parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler); return parameterHandler; } public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler, ResultHandler resultHandler, BoundSql boundSql) { ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds); resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler); return resultSetHandler; } public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql); statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler); return statementHandler; } public Executor newExecutor(Transaction transaction, ExecutorType executorType, boolean autoCommit) { executorType = executorType == null ? defaultExecutorType : executorType; executorType = executorType == null ? ExecutorType.SIMPLE : executorType; Executor executor; if (ExecutorType.BATCH == executorType) { executor = new BatchExecutor(this, transaction); } else if (ExecutorType.REUSE == executorType) { executor = new ReuseExecutor(this, transaction); } else { executor = new SimpleExecutor(this, transaction); } if (cacheEnabled) { executor = new CachingExecutor(executor, autoCommit); } executor = (Executor) interceptorChain.pluginAll(executor); return executor; } pluginAll

InterceptorChain的pluginAll就是遍历所有的拦截器,然后调用各个拦截器的plugin方法。

public Object pluginAll(Object target) { for (Interceptor interceptor : interceptors) { target = interceptor.plugin(target); } return target; } default Object plugin(Object target) { return Plugin.wrap(target, this); } // Plugin public static Object wrap(Object target, Interceptor interceptor) { // 1.3.1 获取所有要增强的方法 Map type = target.getClass(); Class[] interfaces = getAllInterfaces(type, signatureMap); if (interfaces.length > 0) { // 1.3.2 注意这个Plugin就是自己 return Proxy.newProxyInstance(type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)); } return target; }

而每个 Interceptor 的 plugin 方法,都是会来到 Plugin.wrap 方法,这个逻辑有一点点小复杂,我们对其中比较关键的两步拆解开。

获取所有要增强的方法

getSignatureMap方法: 首先会拿到拦截器这个类的 @Interceptors注解,然后拿到这个注解的属性 @Signature注解集合,然后遍历这个集合,遍历的时候拿出 @Signature注解的type属性(Class类型),然后根据这个type得到带有method属性和args属性的Method。由于 @Interceptors注解的 @Signature属性是一个属性,所以最终会返回一个以type为key,value为Set< Method >的Map。

private static Map, Set> signatureMap = new HashMap(); for (Signature sig : sigs) { // 逐个方法名、参数解析,确保能代理到这些方法 Set methods = signatureMap.computeIfAbsent(sig.type(), k -> new HashSet()); try { Method method = sig.type().getMethod(sig.method(), sig.args()); methods.add(method); } // catch ...... } return signatureMap; }

比如下面这个 @Interceptors注解会返回一个key为Executor,value为集合(这个集合只有一个元素,也就是Method实例,这个Method实例就是Executor接口的update方法,且这个方法带有MappedStatement和Object类型的参数)。这个Method实例是根据 @Signature的method和args属性得到的。如果args参数跟type类型的method方法对应不上,那么将会抛出异常。

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

再比如:我们开头的PerformanceInterceptor指定了拦截类型是StatementHandler的,那他signatureMap就是 StatementHandler–>query

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

那么,如果这时候有newParameterHandler、newResultSetHandler、newExecutor进入判断interfaces.length > 0是不会满足要创建代理对象的条件的,只有 newStatementHandler符合getAllInterfaces(type, signatureMap) 符合要创建代理对象,也就是Plugin!

Class[] interfaces = getAllInterfaces(type, signatureMap); if (interfaces.length > 0) { // 1.3.2 注意这个Plugin就是自己 return Proxy.newProxyInstance(type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)); } 创建Plugin对象

最后,它会在 Proxy.newProxyInstance 时创建代理对象,请注意,这里传入了一个 Plugin 对象,也就是当前我们正在看的这个类,对,它本身实现了 InvocationHandler :

public class Plugin implements InvocationHandler { // 目标对象 private final Object target; // 拦截器对象 private final Interceptor interceptor; // 记录了@Signature注解的信息 private final Map


【本文地址】

公司简介

联系我们

今日新闻


点击排行

实验室常用的仪器、试剂和
说到实验室常用到的东西,主要就分为仪器、试剂和耗
不用再找了,全球10大实验
01、赛默飞世尔科技(热电)Thermo Fisher Scientif
三代水柜的量产巅峰T-72坦
作者:寞寒最近,西边闹腾挺大,本来小寞以为忙完这
通风柜跟实验室通风系统有
说到通风柜跟实验室通风,不少人都纠结二者到底是不
集消毒杀菌、烘干收纳为一
厨房是家里细菌较多的地方,潮湿的环境、没有完全密
实验室设备之全钢实验台如
全钢实验台是实验室家具中较为重要的家具之一,很多

推荐新闻


图片新闻

实验室药品柜的特性有哪些
实验室药品柜是实验室家具的重要组成部分之一,主要
小学科学实验中有哪些教学
计算机 计算器 一般 打孔器 打气筒 仪器车 显微镜
实验室各种仪器原理动图讲
1.紫外分光光谱UV分析原理:吸收紫外光能量,引起分
高中化学常见仪器及实验装
1、可加热仪器:2、计量仪器:(1)仪器A的名称:量
微生物操作主要设备和器具
今天盘点一下微生物操作主要设备和器具,别嫌我啰嗦
浅谈通风柜使用基本常识
 众所周知,通风柜功能中最主要的就是排气功能。在

专题文章

    CopyRight 2018-2019 实验室设备网 版权所有 win10的实时保护怎么永久关闭