MyBatis在四大对象的创建过程中,都会有插件进行介入。 插件可以利用动态代理机制一层层的包装目标对象,而实现在目标对象执行目标方法之前进行拦截。
MyBatis允许在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis允许使用插件来拦截的方法调用包括:
- Executor (update, query, flushStatements, commit, rollback,getTransaction, close, isClosed)
- ParameterHandler (getParameterObject, setParameters)
- ResultSetHandler (handleResultSets, handleOutputParameters)
- StatementHandler (prepare, parameterize, batch, update, query)
其中,MyBatis中插件开发的具体测试代码下载地址:https://download.youkuaiyun.com/download/bingbeichen/10540317。
1. 插件原理
在四大对象创建时,所创建的对象均需经过interceptorChain.pluginAll();
方法对其进行包装,而不是直接返回的。
包装过程即获取到所有的Interceptor(拦截器,其是插件需要实现的接口),并调用interceptor.plugin(target);
方法返回target经过包装后的对象。
插件机制即是使用插件为目标对象创建一个代理对象,代理对象可以拦截到目标对象,控制目标对象各个方法的执行,类似于Spring的AOP编程。
2. 插件开发步骤
第一步,编写插件类实现Interceptor接口,并使用@Intercepts注解完成插件签名。
@Intercepts(@Signature(type = StatementHandler.class, method = "parameterize", args = { Statement.class }))
public class MyFirstPlugin implements Interceptor {
/**
* 拦截目标方法执行
*
* 实现:显示查询1号员工信息,实则查询4号员工信息
*/
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("myFirstPlugin.intercept() : " + invocation.getMethod());
Object target = invocation.getTarget();
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;
}
/**
* 生成动态代理对象,可以使用MyBatis提供的Plugin类的wrap方法
*/
@Override
public Object plugin(Object target) {
System.out.println("myFirstPlugin.plugin() : 待包装的对象为" + target);
// 获取包装后的动态代理对象
Object wrap = Plugin.wrap(target, this);
return wrap;
}
/**
* 注入插件配置时设置的属性
*/
@Override
public void setProperties(Properties properties) {
System.out.println("myFirstPlugin.setProperties() : 插件配置的信息为" + properties);
}
}
第二步,在全局配置文件中注册自定义插件。
<plugins>
<plugin interceptor="com.qiaobc.mybatis.plugin.MyFirstPlugin"></plugin>
</plugins>
3. 多插件运行流程
按照插件配置顺序调用插件plugin()方法,生成被拦截对象的动态代理对象。
多个插件依次生成目标对象的代理对象,层层包裹,先声明的先包裹,形成代理链。目标方法执行时依次从外到内执行插件的intercept()方法。
多个插件情况下,我们往往需要在某个插件中分离出目标对象。可以借助MyBatis提供的SystemMetaObject类来进行获取最后一层的h以及target属性的值。示例代码段如下:
/**
* 从代理链中分离真实被代理对象
*/
//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);