一:概述 mybatis插件可以帮我们对mybatis的四大对象进行拦截处理,要使用好插件,我们需要先了解四大对象的作用,这样才能根据需求定义插件。
二:mybatis的四大对象
1.Executor: 负责调用StatementHandler操作数据库,以及二级缓存的处理(使用CachingExecutor进行包装)定义插件可以修改原生sql
2.StatementHandler: 负责sql脚本的执行以及mybatis一级缓存的处理,定义插件可以修改原生的sql,例如分页插件,数据权限等
3.ParameterHandler: 负责对参数的处理,定义插件可以修改对参数的处理
4.ResultSetHandler: 负责对结果集的处理,定义插件拦截可以修改对结果集处理的逻辑
三:自定义插件
1. 实现Interceptor接口
2.利用@Intercepts注解和@Signature注解定义需要拦截的方法
3.intercept方法中编写需要拦截后需要的代码逻辑
@Intercepts(value = {@Signature(type = Executor.class,
method = "query",
args = {MappedStatement.class,Object.class, RowBounds.class, ResultHandler.class})})
public class MyInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
Method method = invocation.getMethod();
Executor target = (Executor) invocation.getTarget();
Object[] args = invocation.getArgs();
MappedStatement ms = (MappedStatement)args[0];
BoundSql boundSql = ms.getBoundSql(args[1]);
String sql = boundSql.getSql();
//TODO 2021/12/04 可以对原sql进行修改
System.out.println("原sql:" + sql);
return invocation.proceed();
}
}
4.将自定义的插件加入到插件链中
@Configuration
public class InterceptorConfig {
@Autowired
private SqlSessionFactory sqlSessionFactory;
@PostConstruct
public void myInterceptor() {
sqlSessionFactory.getConfiguration().addInterceptor(new MyInterceptor());
}
}
四:插件的原理
以Executor为例:
在生成Executor的时候会对比遍历所有的插件对Executor进行代理(多层代理)。代码在Configuration的newExecutor中:
executor = (Executor) interceptorChain.pluginAll(executor);
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
循环所有的插件对Executor对象进行包装,默认会调用Plugin的wrap()方法生成代理类
public static Object wrap(Object target, Interceptor interceptor) {
//解析Intercepts注解 保存
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
//取得要改变行为的类(ParameterHandler|ResultSetHandler|StatementHandler|Executor)
Class<?> type = target.getClass();
//取得接口
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
//产生代理 所以之后会执行Plugin的invoke方法
if (interfaces.length > 0) {
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
//取Intercepts注解
Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
// issue #251
//必须得有Intercepts注解,没有报错
if (interceptsAnnotation == null) {
throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
}
//value是数组型,Signature的数组
Signature[] sigs = interceptsAnnotation.value();
//每个class里有多个Method需要被拦截,所以这么定义
// 解析Signature注解中的信息
Map<Class<?>, Set<Method>> signatureMap = new HashMap<Class<?>, Set<Method>>();
for (Signature sig : sigs) {
Set<Method> methods = signatureMap.get(sig.type());
if (methods == null) {
methods = new HashSet<Method>();
signatureMap.put(sig.type(), methods);
}
try {
//获取Signature注解中配置的method
Method method = sig.type().getMethod(sig.method(), sig.args());
methods.add(method);
} catch (NoSuchMethodException e) {
throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
}
}
return signatureMap;
}
经过多层代理之后,返回之后就会依次执行Plugin的invoke方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
//看哪些方法需要拦截
if (methods != null && methods.contains(method)) {
//调用Interceptor.intercept,也即插入了我们自己的逻辑
return interceptor.intercept(new Invocation(target, method, args));
}
//不是需要拦截的方法,执行原来逻辑
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
|