所谓的插件其实就是方法拦截器,mybatis四大组件(Executor,StatementHandler,ParameterHandler,ResultSetHandler)在运行过程中,对四大组件方法拦截并增强,底层用的jdk的动态代理实现,常见的插件有分页插件,mybatis-plus中的MybatisPlusInterceptor,自定义插件,如在插入数据库前设置一些默认的字段(这个在mybatis-plus中不需要拦截器实现)等。
先用mybatis中的Interceptor对普通方法拦截,加强理解
接口:
package mybatis;
/**
* 接口,jdk动态代理必须有接口
*/
public interface TestInterface {
String test();
String test(String arg1);
}
实现类:
package mybatis;
/**
* 实现类
* 我们这里拟定目标,只增强待参数的test 方法
*/
public class TestInterfaceImpl implements TestInterface{
@Override
public String test() {
return "无参";
}
@Override
public String test(String arg1) {
return arg1;
}
}
方法拦截器
package mybatis;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import java.util.Properties;
/**
* 拦截器,作用:
* 指定拦截哪个类的哪个方法(Intercepts注解)
* 方法的增强逻辑(intercept方法)
* 对目标对象包装,创建动态代理代替目标对象(plugin方法)
*/
// 指定拦截哪个接口的哪个方法
@Intercepts(
{
@Signature(type = TestInterface.class, method = "test", args = {String.class})
}
)
public class MyInterceptor implements Interceptor {
// 增强逻辑写在这里,invocation 里可以获取目标对象,参数信息
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object[] args = invocation.getArgs();
// 拦截的是TestInterface接口实现类的方法,所以可以直接强转
TestInterface target = (TestInterface) invocation.getTarget();
System.out.println("进入方法拦截。。。。。");
return target.test(args[0].toString());
}
// 创建代理对象提供给客户端,底层用的jdk的动态代理
@Override
public Object plugin(Object target) {
return Interceptor.super.plugin(target);
}
// 目前没用上
@Override
public void setProperties(Properties properties) {
Interceptor.super.setProperties(properties);
}
}
单元测试类:
@Test
public void testPlug() {
// 需要增强的对象
TestInterface testInterface = new TestInterfaceImpl();
// 创建两个拦截器对象,一个目标可以被多个拦截器包装,一层一层的执行
Interceptor myInterceptor = new MyInterceptor();
Interceptor myInterceptor2 = new MyInterceptor();
//执行一次wrap方法就创建一个代理对象,wrap执行后相当于:Proxy1(target)
testInterface = (TestInterface) Plugin.wrap(testInterface, myInterceptor);
// 第二次wrap方法执行后相当于:Proxy(Proxy1(target))
testInterface = (TestInterface) Plugin.wrap(testInterface, myInterceptor2);
// 执行被拦截的方法
System.out.println(testInterface.test("hello"));
}
结果:
进入方法拦截。。。。。
进入方法拦截。。。。。
hello
Process finished with exit code 0
可以看到两个增强器的方法都执行了
mybatis 框架只对四大组件设置了插件机制
1、组件是怎么添加到框架中的,拦截器对象保存在哪?
项目启动时解析mybatis 配置文件,解析xml中的plugins 标签或者配置类中通过java代码直接添加。
保存在一个集合中,InterceptorChain 类中的List interceptors 成员变量中
2、何时创建代理对目标进行包装?
Configuration 创建四大组件时,如果有插件,就会创建代理对象,可以是多个,多个代理对象嵌套形成责任链,源码如下:
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;
}
public Executor newExecutor(Transaction transaction) {
return newExecutor(transaction, defaultExecutorType);
}
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
// 用所有的拦截器包装
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
包装源码:
interceptorChain.pluginAll 最终会调用到Plugin.wrap(target, this) 方法,过程比较简单, wrap() 方法源码
package org.apache.ibatis.plugin;
/**
* @author Clinton Begin
*/
public class Plugin implements InvocationHandler {
private final Object target;
private final Interceptor interceptor;
private final Map, Set> signatureMap;
// 创建动态代理,将interceptor(拦截器中对目标增强逻辑) 包装成Plugin对象 (动态代理的InvocationHandler)
public static Object wrap(Object target, Interceptor interceptor) {
Map, Set> signatureMap = getSignatureMap(interceptor);
Class> type = target.getClass();
Class>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
// jdk 动态代理,代理对象执行目标方法前先执行invoke方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
Set methods = signatureMap.get(method.getDeclaringClass());
if (methods != null && methods.contains(method)) {
// @Signature 注解中标注了的方法才回去拦截
return interceptor.intercept(new Invocation(target, method, args));
}
// 否则直接执行目标方法
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
}
3、只拦截目标类的目标方法(其他普通方法不拦截)的逻辑在哪实现,如何调用到Interceptor的interceptor方法?
上面Plugin类中的invoke方法执行时,会对方法进行判断是否需要增强,然后执行interceptor.intercept
mybatis 的插件原理其实还是比较简单的,对四大组件指定的方法实现方法增强,多个拦截器就会创建多个代理对象,一层层包装,形成拦截器链,逐个执行。
另:
四大组件一般就Executor(最常见) 和StatementHandler用的多一些,mybatis-plus中
MybatisPlusInterceptor对Executor的query,update,StatementHandler的prepare 方法都进行了拦截,在其中设置了内部拦截器(InnerInterceptor),在不同执行时机都预留了接口,实现拦截器时直接都是实现内部拦截器,这样就不需要创建多个代理对象,性能应该会好一点。