- MyBatis在四大对象的创建过程中,都会有插件进行介入。插件可以利用动态代理机制一层层的包装目标对象,而实现在目标对象执行目标方法之前进行拦截的效果。
- MyBatis允许在已映射语句执行过程中的某一点进行拦截调用
- 默认情况下,MyBatis允许使用插件来拦截的方法调用包括
- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
- ParameterHandler (getParameterObject, setParameters)
- ResultSetHandler (handleResultSets, handleOutputParameters)
- StatementHandler (prepare, parameterize, batch, update, query)
插件开发
编写插件实现Interceptor接口,并使用@Intercepts 注解完成插件签名。
@Intercepts({
@Signature(type = StatementHandler.class, method = "parameterize", args = java.sql.Statement.class)
})
public class MyFirstPlugin implements Interceptor{}
在全局配置文件中注册插件
<!--这个步骤是在myBatis-config.xml中完成的--> <!--plugins:注册插件--> <plugins> <plugin interceptor="com.zh.mybatis.dao.MyFirstPlugin"> <!--这儿传递的参数会在setProperties方法中处理--> <property name="username" value="root"/> <property name="password" value="root"/> </plugin> </plugins>
插件原理
- 按照插件注解声明,按照插件配置顺序调用插件plugin方法,生成被拦截对象的动态代理。
- 多个插件依次生成目标的代理对象,层层包裹,先声明的的先包裹,形成代理连。
- 目标方法执行时依次从外到内执行插件的intercept方法。
- 多个插件情况下,我们往往需要在某个插件中分离出目标对象。可以借助MyBatis提供的SystemMetaObject类来进行获取最后一层的h以及target属性的值。
Interceptor接口
- Intercept:拦截目标方法执行
- plugin:生成动态代理对象,可以使用MyBatis提供的Plugin类的wrap方法。
- setProperties:注入插件配置是设置的属性。
常用代码:
从代理连中分离真实被代理对象
// 1. 分离代理对象。由于会形成多次代理,所以需要通过一个while循环分离出最终被代理对象,从而方便提取信息。
MetaObject metaObject = SystemMetaObject.forObject(target);
while(metaObject.hasGetter("h")) {
Object h = metaObject.getValue("h");
metaObject = SystemMetaObject.forObject(h);
}
// 2. 获取到代理对象中包含的被代理的真实对象
Object obj = metaObject.getValue("target");
// 3. 获取被代理对象的MetaObject方便进行信息提取
MetaObject forObject = SystemMetaObject.forObject(obj);
// 经典代码
package com.zh.mybatis.dao;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import java.util.Properties;
/**
* 插件签名:
* 告诉MyBatis当前插件用来拦截哪个对象的哪个方法
*/
@Intercepts({
@Signature(type = StatementHandler.class, method = "parameterize", args = java.sql.Statement.class)
})
public class MyFirstPlugin implements Interceptor {
/**
* intercept
* 拦截目标方法的执行
*
* @param invocation
* @return
* @throws Throwable
*/
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("MyFirstPlugin.intercept: " + invocation.getMethod());
// 动态改变一下sql运行的参数:以前1号员工,实际从数据库查询4号员工的信息
Object target = invocation.getTarget();
System.out.println("当前拦截到的对象:" + invocation.getTarget());
// 拿到StatementHandler ==> ParameterHandler ==> parameterObject
// 拿到target的元数据
MetaObject metaObject = SystemMetaObject.forObject(target);
Object value = metaObject.getValue("parameterHandler.parameterObject");
System.out.println("sql语句用的参数时:" + value);
metaObject.setValue("parameterHandler.parameterObject", 5);
// 执行目标方法
Object proceed = invocation.proceed();
// 返回执行后的返回值
return proceed;
}
/**
* plugin
* 包装目标对象:包装:为目标对象创建一个代理对象
*
* @param target
* @return
*/
@Override
public Object plugin(Object target) {
System.out.println("MyFirstPlugin.plugin: " + target);
// 我们可以借助Plugin的warp的方法来使用当前的Interceptor包装目标对象
Object wrap = Plugin.wrap(target, this);
// 返回为当前target创建动态代理
return wrap;
}
/**
* setProperties
* 将插件注册时的property属性设置进来
*
* @param properties
*/
@Override
public void setProperties(Properties properties) {
System.out.println("MyFirstPlugin.setProperties插件配置的信息:" + properties);
}
}