MyBatis插件开发原理

本文介绍了MyBatis插件的工作原理,通过责任链模式和动态代理实现对四大对象的拦截。在创建Executor、ParameterHandler、ResultSetHandler和StatementHandler的过程中,MyBatis会调用interceptChain.pluginAll()方法进行对象包装。文章通过案例展示了如何创建一个拦截器StatementHandlerInterceptor,实现了对StatementHandler的代理,并在全局配置文件中注册。总结了插件开发的步骤和执行流程,强调了插件按照声明顺序形成代理链,拦截目标方法执行。

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

一、前言

MyBatis采用责任链模式,通过动态代理组织多个插件(拦截器),通过这些插件可以改变MyBatis的默认行为(诸如SQL重写之类的),由于插件会深入到MyBatis的核心,因此在编写自己的插件前最好了解下它的原理,以便写出安全高效的插件。
MyBatis在四大对象的创建过程中,都会有插件进行介入。插件可以利用动态代理机制一层层的包装目标对象,而实现目标对象在执行目标方法之前进行拦截的效果。
插件介入指的是:创建过程中都会涉及到调用interceptChain.pluginAll()方法对四大对象进行重新包装,返回一个代理对象。
MyBatis 允许在已映射语句执行过程中的某一点进行拦截调用,默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:

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

插件开发是基于动态代理实现的,所有有必要对动态代理有所了解。

我们可以看一下MyBatis是怎么创建这四大接口对象的。找到源码BaseStatementHandler

this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);

进入configuration类,下面几处都是在创建newParameterHandler,ResultSetHandler,StatementHandler这几个对象,在调用的过程中,大家都看到了都使用了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;
}

点进interceptorChain.pluginAll方法里面

/**
*每一个拦截器对目标类都进行一次代理
*@target
*@return 层层代理后的对象
**/
public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target);
    }
    return target;
}

这一段代码可以看到:获取所有的Interceptor(拦截器),我们如果需要自定义拦截器就得实现Interceptor这个接口。然后调用interceptor.plugin(target);返回target包装之后的对象
所以,我们可以使用插件为目标对象创建一个代理对象,这跟我们学习Struts2的拦截器,Spring的AOP一样,其实都是动态代理,面向切面的编程。

下面我们通过案例为StatementHandler创建代理对象

二、案例

搭建一个新的Maven工程MyBatisPlugins

创建一个拦截器StatementHandlerInterceptor

/**
 * 定义插件StatementHandlerInterceptor
 * 告诉MyBatis当前插件用来拦截哪个对象的哪个方法
 */
@Intercepts({@Signature(type=StatementHandler.class,method="parameterize",args=java.sql.Statement.class)})
public class StatementHandlerInterceptor implements Interceptor {

	/**
	 * 拦截目标对象的目标方法的执行
	 */
	@Override
	public Object intercept(Invocation invocation) throws Throwable {
		// 执行目标方法
		Object proceed = invocation.proceed();
		// 返回执行后的返回值
		return proceed;
	}

	/**
	 * 包装目标对象 包装:为目标对象创建一个代理对象
	 * 根据当前拦截的对象创建了一个动态代理对象。代理对象的InvocationHandler处理器为新建的Plugin对象
	 * 官方推荐实现方式:Plugin.wrap(target, this);
	 */
	@Override
	public Object plugin(Object target) {
		// 我们可以借助Plugin的wrap方法来使用当前Interceptor包装我们目标对象
		System.out.println("StatementHandlerInterceptor...plugin:mybatis将要包装的对象" + target);
		Object wrap = Plugin.wrap(target, this);
		// 返回为当前target创建的动态代理
		return wrap;
	}

	/**
	 * 将插件注册时的property属性设置进来
	 */
	@Override
	public void setProperties(Properties properties) {
		System.out.println("插件配置的信息:"+properties);
	}

}

每一个拦截器都必须实现上面的三个方法,其中:

  • Object intercept(Invocation invocation)是实现拦截逻辑的地方,内部要通过invocation.proceed()显式地推进责任链前进,也就是调用下一个拦截器拦截目标方法。
  • Object plugin(Object target) 就是用当前这个拦截器生成对目标target的代理,实际是通过Plugin.wrap(target,this) 来完成的,把目标target和拦截器this传给了包装函数。
  • setProperties(Properties properties)用于设置额外的参数,参数配置在拦截器的Properties节点里。

注解里描述的是指定拦截方法的签名   [type,method,args] (即对哪种对象的哪种方法进行拦截),它在拦截前用于决断。

将拦截器注册到全局配置文件中

打印测试一下,看看控制台打印效果

StatementHandlerInterceptor...plugin:mybatis将要包装的对象org.apache.ibatis.executor.CachingExecutor@4d639a35
StatementHandlerInterceptor...plugin:mybatis将要包装的对象org.apache.ibatis.scripting.defaults.DefaultParameterHandler@1d74b5ee
StatementHandlerInterceptor...plugin:mybatis将要包装的对象org.apache.ibatis.executor.resultset.DefaultResultSetHandler@27f5ad1c
StatementHandlerInterceptor...plugin:mybatis将要包装的对象org.apache.ibatis.executor.statement.RoutingStatementHandler@4229e623

2017-08-20 12:35:29,825 [main] [com.queen.mybatis.mapper.EmpMapper.findEmpById]-[DEBUG] ==>  Preparing: select id,emp_name empName,emp_email empEmail, dept_id deptId from t_emp where id = ? 
2017-08-20 12:35:29,895 [main] [com.queen.mybatis.mapper.EmpMapper.findEmpById]-[DEBUG] ==> Parameters: 1(Integer)
2017-08-20 12:35:29,949 [main] [com.queen.mybatis.mapper.EmpMapper.findEmpById]-[DEBUG] <==      Total: 1
Emp [id=1, empName=queen3aasd21, empEmail=queen123@sina.com, deptId=1]

可以看到四大对象创建的时候都会调用这个拦截器因为都会被拦截下来,但是只有StatementHandler被创建了代理对象。这是为什么呢?大家可以看一下Plugin的源码

大家可以Debug一下源码,您会发现只有StatementHandler被创建了代理对象

三、总结

插件开发步骤如下:

  • 编写插件实现Interceptor接口,并使用@Intercepts注解完成插件签名
  • 在全局配置文件中注册插件

插件的原理:

  • 按照插件注解声明,按照插件配置顺序调用插件plugin方法,生成被拦截对象的动态代理
  • 多个插件依次生成目标对象的代理对象,层层包裹,先声明的先包裹;形成代理链
  • 目标方法执行时依次从外到内执行插件的intercept方法

 

 

=======欢迎大家拍砖,小手一抖,多多点赞哟!=======

版权声明:本文为博主原创文章,允许转载,但转载必须标明出处。

 

<think>首先,用户的问题是关于MyBatis插件的运行原理和如何编写一个自定义插件。用户说:“我想理解MyBatis插件的运行原理并学习如何编写一个插件 请问MyBatis插件机制原理及自定义插件开发教程”。 根据系统级指令: - 所有行内数学表达式必须使用$...$格式。 - 独立公式必须使用$$...$$格式并单独成段。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 回答中引用的段落末尾自然地添加引用标识。 - 回答结构清晰,帮助用户逐步解决问题,保证真实可靠。 参考提供的引用: - [^1]:MyBatis插件的运行原理,解释插件MyBatis执行过程中的作用。 - [^2]:MyBatis插件是拦截器功能(代理模式),开发门槛较高,需要了解源码。 - [^3]:插件机制原理:针对四大对象(Executor, ParameterHandler, ResultSetHandler, StatementHandler),使用动态代理生成代理对象。 - [^4]:插件作用:在SQL执行过程中做自定义操作。实现插件需要实现Interceptor接口,使用@Intercepts和@Signature注解指定拦截方法。 - [^5]:动态代理机制,通过invocation.proceed()调用原操作。 我需要构建一个结构清晰的回答: 1. **解释MyBatis插件的运行原理**:基于引用,核心是动态代理机制,拦截四大对象的方法。 2. **提供自定义插件开发教程**:逐步指导如何编写一个插件,包括实现Interceptor接口、使用注解等。 3. **生成相关问题**:在回答结束时,添加相关问题部分。 回答结构: - **引言**:简要介绍MyBatis插件机制。 - **运行原理部分**:详细解释动态代理、四大对象和拦截过程。 - **开发教程部分**:分步说明如何开发一个自定义插件,可能包括代码示例。 - **结论**:总结要点。 - **相关问题**:生成3-5个相关问题。 使用引用标识:在相关段落末尾自然地添加[^1], [^2]等,引用提供的引用内容。 由于引用是给定的,我需要基于它们来回答: - [^1]:插件MyBatis执行过程中发挥作用。 - [^2]:插件是拦截器功能,使用代理模式。 - [^3]:插件针对四大对象,使用动态代理。 - [^4]:实现步骤和拦截方法。 - [^5]:动态代理细节。 在回答中,我会自然地融入这些引用。 生成相关问题:基于用户问题,生成类似示例中的相关问题,如: 1. 如何优化MyBatis插件的性能? 2. MyBatis插件有哪些常见应用场景? 3. 开发MyBatis插件时需要注意哪些问题? 确保语言是中文。 现在,规划回答大纲: 1. **MyBatis插件机制原理** - 介绍插件是拦截器,基于代理模式。 - 解释四大对象:Executor, ParameterHandler, ResultSetHandler, StatementHandler。 - 描述动态代理过程:在对象创建时生成代理对象,拦截方法调用。 - 引用[^3]和[^5]说明动态代理机制。 2. **自定义插件开发教程** - 步骤1:实现Interceptor接口。 - 步骤2:使用@Intercepts和@Signature注解指定拦截点。 - 步骤3:实现intercept方法,处理拦截逻辑。 - 步骤4:在MyBatis配置中注册插件。 - 可能提供简单代码示例,如一个日志插件或分页插件。 - 引用[^4]和[^2]指导实现。 3. **总结** - 强调核心原理是动态代理。 - 鼓励实践。 4. **相关问题** - 列出3-5个相关问题。 在代码示例中,使用代码块格式,如示例中的Python代码。 最后,确保回答真实可靠,基于提供的引用和一般知识。</think>### MyBatis插件机制原理及自定义插件开发教程 MyBatis插件MyBatis框架提供的一种扩展机制,允许开发者在SQL执行的关键点上插入自定义逻辑。其核心原理基于动态代理和拦截器模式,通过拦截MyBatis的四大核心对象的方法来实现功能增强。下面我将逐步解释运行原理并提供详细的自定义插件开发教程。 #### 一、MyBatis插件运行原理 MyBatis插件的核心是动态代理机制。MyBatis在初始化过程中会为四大核心对象(Executor、ParameterHandler、ResultSetHandler、StatementHandler)创建代理对象。当这些对象的方法被调用时,代理对象会拦截方法执行,并调用插件的`intercept`方法,开发者可以在其中添加自定义逻辑。最后,通过`invocation.proceed()`调用原始方法,确保SQL执行流程继续[^3][^5]。 具体原理步骤: 1. **拦截点定义**:MyBatis插件只能拦截四大对象的方法: - **Executor**:负责SQL执行全过程,包括update、query、commit等。 - **ParameterHandler**:处理SQL参数组装,如setParameters。 - **ResultSetHandler**:处理结果集映射,如handleResultSets。 - **StatementHandler**:执行SQL语句,如prepare、parameterize。 这些对象在创建时会被“包装”成代理对象[^4]。 2. **动态代理过程**: - 当MyBatis启动时,它会检查所有注册的插件。 - 对于每个插件MyBatis使用`Plugin.wrap()`方法生成目标对象的动态代理。 - 代理对象基于JDK动态代理实现,通过`InvocationHandler`接口的`invoke`方法拦截调用。 - 如果被调用的方法匹配插件的拦截规则(由注解定义),则执行插件的`intercept`方法;否则直接调用原始方法[^3][^5]。 3. **注解驱动**:插件使用`@Intercepts`和`@Signature`注解指定拦截规则: - `@Signature`定义要拦截的方法签名(类型、方法名、参数类型)。 - MyBatis在运行时解析这些注解,生成拦截映射表[^4]。 这种机制确保了插件的高扩展性,同时保持MyBatis核心流程的稳定性[^1]。例如,一个分页插件可能拦截StatementHandler的prepare方法,在SQL执行前添加分页逻辑[^2]。 #### 二、自定义插件开发教程 开发一个MyBatis插件需要实现`Interceptor`接口,并使用注解定义拦截点。以下是详细步骤(以开发一个简单的SQL日志插件为例,该插件在SQL执行前打印日志): ##### 步骤1:创建插件类并实现Interceptor接口 首先,定义一个类实现`org.apache.ibatis.plugin.Interceptor`接口,并实现三个方法: - `intercept(Invocation invocation)`:核心方法,用于处理拦截逻辑。 - `plugin(Object target)`:用于生成代理对象,通常直接调用`Plugin.wrap(target, this)`。 - `setProperties(Properties properties)`:可选,用于接收配置文件中的参数。 ```java import org.apache.ibatis.executor.statement.StatementHandler; import org.apache.ibatis.plugin.*; import java.sql.Connection; import java.util.Properties; @Intercepts({ @Signature( type = StatementHandler.class, // 拦截StatementHandler对象 method = "prepare", // 拦截prepare方法 args = {Connection.class, Integer.class} ) }) public class SqlLoggerPlugin implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { // 自定义逻辑:在SQL执行前打印日志 System.out.println("[MyBatis Plugin] SQL准备执行..."); // 调用原始方法继续执行SQL return invocation.proceed(); } @Override public Object plugin(Object target) { // 生成代理对象 return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { // 可接收mybatis-config.xml中的配置参数 } } ``` ##### 步骤2:注册插件MyBatis配置 在MyBatis配置文件(如`mybatis-config.xml`)中添加插件声明: ```xml <configuration> <plugins> <plugin interceptor="com.example.SqlLoggerPlugin" /> </plugins> </configuration> ``` ##### 步骤3:测试插件 - 当MyBatis执行SQL时(如调用`SqlSession`的查询方法),插件会自动拦截StatementHandler的prepare方法。 - 控制台会输出日志:`[MyBatis Plugin] SQL准备执行...`,然后SQL继续执行。 ##### 关键注意事项: - **拦截点选择**:根据需求选择正确的对象和方法。例如: - 修改SQL参数:拦截ParameterHandler的setParameters方法。 - 分页功能:拦截StatementHandler的prepare方法[^2]。 - **性能考虑**:插件会增加代理层开销,避免在频繁调用的方法中添加复杂逻辑。 - **错误处理**:在intercept方法中捕获异常,避免阻断原始流程。 - **依赖管理**:确保插件类在MyBatis类路径中可用。 开发完成后,插件可以无缝集成到MyBatis中,提供如性能监控、SQL审计等功能[^4]。 #### 三、总结 MyBatis插件机制的核心是动态代理,它通过拦截四大对象的方法实现功能扩展。开发自定义插件时,需实现Interceptor接口并使用注解指定拦截点,最后在配置中注册。这种设计既灵活又强大,但需要开发者对MyBatis内部机制有较深理解[^1][^3]。通过本教程,您可以快速入门插件开发,后续可尝试更复杂的场景如分页或缓存优化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值