互联网技术 · 2024年2月13日

mybatis拦截器实现原理和实例简介

这篇文章主要介绍了简单了解mybatis*实现原理及实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下

例行惯例,先看些基本概念:

1 *的作用就是我们可以拦截某些方法的调用,在目标方法前后加上我们自己逻辑

2 Mybatis*设计的一个初衷是为了供用户在某些时候可以实现自己的逻辑而不必去动Mybatis固有的逻辑。

自定义*

* mybatis 自定义*

* 三步骤:

* 1 实现 {@link Interceptor} 接口

* 2 添加拦截注解 {@link Intercepts}

* 3 配置文件中添加*

* 1 实现 {@link Interceptor} 接口

* 具体作用可以看下面代码每个方法的注释

* 2 添加拦截注解 {@link Intercepts}

* mybatis *默认可拦截的类型只有四种,即四种接口类型 Executor、StatementHandler、ParameterHandler 和 ResultSetHandler

* 对于我们的自定义*必须使用 mybatis 提供的注解来指明我们要拦截的是四类中的哪一个类接口

* 具体规则如下:

* a:Intercepts *: 标识我的类是一个*

* b:Signature 署名: 则是指明我们的*需要拦截哪一个接口的哪一个方法

* type 对应四类接口中的某一个,比如是 Executor

* method 对应接口中的哪类方法,比如 Executor 的 update 方法

* args 对应接口中的哪一个方法,比如 Executor 中 query 因为重载原因,方法有多个,args 就是指明参数类型,从而确定是哪一个方法

* 3 配置文件中添加*

* *其实就是一个 plugin,在 mybatis 核心配置文件中我们需要配置我们的 plugin :

* plugin interceptor=”liu.york.mybatis.study.plugin.MyInterceptor”

* property name=”username” value=”LiuYork”/

* property name=”password” value=”123456″/

* /plugin

* *顺序

* 1 不同*顺序:

* Executor – ParameterHandler – StatementHandler – ResultSetHandler

* 2 对于同一个类型的*的不同对象拦截顺序:

* 在 mybatis 核心配置文件根据配置的位置,拦截顺序是 从上往下

public class MyInterceptor implements Interceptor {

* 这个方法很好理解

* 作用只有一个:我们不是拦截方法吗,拦截之后我们要做什么事情呢?

* 这个方法里面就是我们要做的事情

* 解释这个方法前,我们一定要理解方法参数 {@link Invocation} 是个什么鬼?

* 1 我们知道,mybatis*默认只能拦截四种类型 Executor、StatementHandler、ParameterHandler 和 ResultSetHandler

* 2 不管是哪种代理,代理的目标对象就是我们要拦截对象,举例说明:

* 比如我们要拦截 {@link Executor#update(MappedStatement ms, Object parameter)} 方法,

* 那么 Invocation 就是这个对象,Invocation 里面有三个参数 target method args

* target 就是 Executor

* method 就是 update

* args 就是 MappedStatement ms, Object parameter

* 如果还是不能理解,我再举一个需求案例:看下面方法代码里面的需求

* 该方法在运行时调用

@Override

public Object intercept(Invocation invocation) throws Throwable {

* 需求:我们需要对所有更新操作前打印查询语句的 sql 日志

* 那我就可以让我们的自定义* MyInterceptor 拦截 Executor 的 update 方法,在 update 执行前打印sql日志

* 比如我们拦截点是 Executor 的 update 方法 : int update(MappedStatement ms, Object parameter)

* 那当我们日志打印成功之后,我们是不是还需要调用这个query方法呢,如何如调用呢?

* 所以就出现了 Invocation 对象,它这个时候其实就是一个 Executor,而且 method 对应的就是 query 方法,我们

* 想要调用这个方法,只需要执行 invocation.proceed()

/* 因为我拦截的就是Executor,所以我可以强转为 Executor,默认情况下,这个Executor 是个 SimpleExecutor */

Executor executor = (Executor)invocation.getTarget();

* Executor 的 update 方法里面有一个参数 MappedStatement,它是包含了 sql 语句的,所以我获取这个对象

* 以下是伪代码,思路:

* 1 通过反射从 Executor 对象中获取 MappedStatement 对象

* 2 从 MappedStatement 对象中获取 SqlSource 对象

* 3 然后从 SqlSource 对象中获取获取 BoundSql 对象

* 4 最后通过 BoundSql#getSql 方法获取 sql

MappedStatement mappedStatement = ReflectUtil.getMethodField(executor, MappedStatement.class);

SqlSource sqlSource = ReflectUtil.getField(mappedStatement, SqlSource.class);

BoundSql boundSql = sqlSource.getBoundSql(args);

String sql = boundSql.getSql();

logger.info(sql);

* 现在日志已经打印,需要调用目标对象的方法完成 update 操作

* 我们直接调用 invocation.proceed() 方法

* 进入源码其实就是一个常见的反射调用 method.invoke(target, args)

* target 对应 Executor对象

* method 对应 Executor的update方法

* args 对应 Executor的update方法的参数

return invocation.proceed();

* 这个方法也很好理解

* 作用就只有一个:那就是Mybatis在创建*代理时候会判断一次,当前这个类 MyInterceptor 到底需不需要生成一个代理进行拦截,

* 如果需要拦截,就生成一个代理对象,这个代理就是一个 {@link Plugin},它实现了jdk的动态代理接口 {@link InvocationHandler},

* 如果不需要代理,则直接返回目标对象本身

* Mybatis为什么会判断一次是否需要代理呢?

* 默认情况下,Mybatis只能拦截四种类型的接口:Executor、StatementHandler、ParameterHandler 和 ResultSetHandler

* 通过 {@link Intercepts} 和 {@link Signature} 两个注解[!–empirenews.page–]三个核心方法都加了详细的注释,而且结合案例需求说明问题

那么多文字不想行看,没关系有概括

总结:

1.在mybatis中可被拦截的类型有四种(按照拦截顺序):

Executor:拦截执行器的方法。

ParameterHandler:拦截参数的处理。

ResultHandler:拦截结果集的处理。

StatementHandler:拦截Sql语法构建的处理。

2.各个参数的含义:

@Intercepts:标识该类是一个*;

@Signature:指明自定义*需要拦截哪一个类型,哪一个方法;

2.1 type:对应四种类型中的一种;

2.2 method:对应接口中的哪类方法(因为可能存在重载方法);

2.3 args:对应哪一个方法;

不知道能否帮助你理解,我的表达能力有限~~~

接下来我们看看 Plugin 类

package org.apache.ibatis.plugin;

* Plugin 类其实就是一个代理类,因为它实现了jdk动态代理接口 InvocationHandler

* 我们核心只需要关注两个方法

* wrap:

* 如果看懂了代码案例1的例子,那么这个方法很理解,这个方法就是 mybatis 提供给开发人员使用的一个工具类方法,

* 目的就是帮助开发人员省略掉 反射解析注解 Intercepts 和 Signature,有兴趣的可以去看看源码 Plugin#getSignatureMap 方法

* invoke:

* 这个方法就是根据 wrap 方法的解析结果,判断当前*是否需要进行拦截,

* 如果需要拦截:将 目标对象+目标方法+目标参数 封装成一个 Invocation 对象,给我们自定义的* MyInterceptor 的 intercept 方法

* 这个时候就刚好对应上了上面案例1中对 intercept 方法的解释了,它就是我们要处理自己逻辑的方法,

* 处理好了之后是否需要调用目标对象的方法,比如上面说的 打印了sql语句,是否还要查询数据库呢?答案是肯定的

* 如果不需要拦截:则直接调用目标对象的方法

* 比如直接调用 Executor 的 update 方法进行更新数据库

class Plugin implements InvocationHandler {

public static Object wrap(Object target, Interceptor interceptor) {

// 省略

@Override

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

// 省略

}

贴一段网上的通用解释吧:

Plugin的wrap方法,它根据当前的Interceptor上面的注解定义哪些接口需要拦截,然后判断当前目标对象是否有实现对应需要拦截的接口,如果没有则返回目标对象本身,如果有则返回一个代理对象。而这个代理对象的InvocationHandler正是一个Plugin。所以当目标对象在执行接口方法时,如果是通过代理对象执行的,则会调用对应InvocationHandler的invoke方法,也就是Plugin的invoke方法。

所以接着我们来看一下该invoke方法的内容。这里invoke方法的逻辑是:如果当前执行的方法是定义好的需要拦截的方法,则把目标对象、要执行的方法以及方法参数封装成一个Invocation对象,再把封装好的Invocation作为参数传递给当前*的intercept方法。如果不需要拦截,则直接调用当前的方法。Invocation中定义了定义了一个proceed方法,其逻辑就是调用当前方法,所以如果在intercept中需要继续调用当前方法的话可以调用invocation的procced方法。

这就是Mybatis中实现Interceptor拦截的一个思想

OpenMagic API

Need more than content? Move into the product flow.

If you are here for model access, pricing, developer docs, or the future API console, the dedicated product path now lives on api.openmagic.ai.

登录免费注册