一、插件开发
MyBatis在四大对象的创建过程中,都会有插件进行介入。 插件可以利用动态代理机制一层层的包装目标对象,而实现在目标对象执行目标方法之前进行拦截的效果。
MyBatis 允许在已映射语句执行过程中的某一点进行拦截调用。
默认情况下, MyBatis 允许使用插件来拦截的方法调用包括:
Executor (update, query, flushStatements, commit, rollback,getTransaction, close, isClosed)
ParameterHandler (getParameterObject, setParameters)
ResultSetHandler (handleResultSets, handleOutputParameters)
StatementHandler (prepare, parameterize, batch, update, query)
在四大对象创建的时候
1、每个创建出来的对象不是直接返回的,而是通过 interceptorChain.pluginAll(parameterHandler);
2、获取到所有的 Interceptor(拦截器)(插件需要实现的接口),调用 Interceptor.plugin(target):返回 target 包装后的对象;
3、插件机制,可以使用插件为目标对象创建一个代理对象:AOP(面向切面)
我们的插件可以为四大对象创建出代理对象,代理对象就可以拦截到四大对象的每一个执行:
核心代码:
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
二、开发单个插件
1、插件开发步骤
① 编写 Interceptor 的实现类;
② 使用 @Intercepts 注解完成插件签名;
③ 将写好的插件注册到全局配置文件中;
案例:
/**
* 完成插件签名
* 告诉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());
//metaObject.setValue("parameterHandler.parameterObject", "3");
//执行目标方法
Object proceed = invocation.proceed();
//返回执行后的返回值
return proceed;
}
/**
* plugin:包装
* 包装目标对象的;包装,为目标对象创建一个代理对象
* @param target
* @return
*/
@Override
public Object plugin(Object target) {
System.out.println("MyFirstPlugin...plugin:MyBatis将要包装的对象" + target);
//借助Plugin的 wrap 方法来使用当前Interceptor 包装我们目标对象
Object wrap = Plugin.wrap(target, this);
//返回当前 target 创建的动态代理
return wrap;
}
/**
* setProperties:
* 将插件注册时 property 属性设置进来
* @param properties
*/
@Override
public void setProperties(Properties properties) {
System.out.println("插件配置的信息" + properties);
}
}
配置:
<!--
plugins:注册插件
-->
<plugins>
<plugin interceptor="com.njf.mybatis.plugin.MyFirstPlugin">
<property name="username" value="root"/>
<property name="password" value="root"/>
</plugin>
</plugins>
测试:
@Test
public void testPlugin() throws IOException {
//1、获取 sqlSessionFactory
SqlSessionFactory sqlSessionFactory = getsqlSessionFactory();
//2、获取 sqlSession 实例,能直接执行已经映射的 SQL 语句
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
//3、获取接口的实现类对象
/**
* 推荐使用
* 会为接口自动的创建一个代理对象,代理对象去执行增删改查方法
*/
EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
//System.out.println(employeeMapper);
//System.out.println(employeeMapper.getClass());
Employee emp = employeeMapper.getEmpById(1);
System.out.println(emp);
} finally {
sqlSession.close();
}
}
运行结果:
2、运行原理
插件会产生目标对象的代理对象。
三、多个插件
如果我们此时又声明了一个插件,如下:
/**
* 完成插件签名
* 告诉MyBatis当前插件用用来拦截哪个对象的哪个方法
*/
@Intercepts(
{
@Signature(type = StatementHandler.class, method = "parameterize", args = java.sql.Statement.class)
}
)
public class MySecondPlugin implements Interceptor {
/**
* intercept:拦截
* 拦截目标对象的目标方法的执行。
* @param invocation
* @return
* @throws Throwable
*/
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("MySecondPlugin...intercept:" + invocation.getMethod());
//执行目标方法
Object proceed = invocation.proceed();
//返回执行后的返回值
return proceed;
}
/**
* plugin:包装
* 包装目标对象的;包装,为目标对象创建一个代理对象
* @param target
* @return
*/
@Override
public Object plugin(Object target) {
System.out.println("MySecondPlugin...plugin:MyBatis将要包装的对象" + target);
//借助Plugin的 wrap 方法来使用当前Interceptor 包装我们目标对象
Object wrap = Plugin.wrap(target, this);
//返回当前 target 创建的动态代理
return wrap;
}
/**
* setProperties:
* 将插件注册时 property 属性设置进来
* @param properties
*/
@Override
public void setProperties(Properties properties) {
System.out.println("插件配置的信息" + properties);
}
}
配置信息:
<!--
plugins:注册插件
-->
<plugins>
<plugin interceptor="com.njf.mybatis.plugin.MyFirstPlugin">
<property name="username" value="root"/>
<property name="password" value="root"/>
</plugin>
<plugin interceptor="com.njf.mybatis.plugin.MySecondPlugin"></plugin>
</plugins>
还是测试上面的方法,运行结果:
原理分析:
多个插件就会产生多层代理。
多个插件原理:
创建动态代理的时候,是按照插件配置顺序创建层层代理对象。
执行目标方法的时候,是按照逆向顺序执行。
四、插件原理
1、按照插件注解声明,按照插件配置顺序调用插件plugin方法,生成被拦截对象的动态代理;
2、多个插件依次生成目标对象的代理对象,层层包裹, 先声明的先包裹;形成代理链;
3、目标方法执行时依次从外到内执行插件的intercept方法;
4、多个插件情况下,我们往往需要在某个插件中分离出目标对象。可以借助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);