mybatis版本 3.5.9
背景
mybatis-plus的分页插件PaginationInnerInterceptor用了很久,但是之前没有去深究它的实现原理,看了一下是利用mybatis的Interceptor拦截器,所以就先来看看它的执行流程了。
简介
MyBatis Interceptor(拦截器)是 MyBatis 框架提供的一个功能,用于拦截和处理 SQL 语句的执行过程。它允许开发者在 SQL 执行的前后进行一些自定义的操作,例如日志记录、权限校验、性能监控等。
使用 MyBatis Interceptor 需要实现 Interceptor(org.apache.ibatis.plugin.Interceptor) 接口,并重写其中的方法,包括 intercept() 方法和 plugin() 方法。intercept() 方法用于自定义拦截逻辑,plugin() 方法用于包装目标对象,返回一个代理对象,实现拦截器功能。可代理的对象包括:
org.apache.ibatis.executor.Executor
org.apache.ibatis.executor.statement.StatementHandler
org.apache.ibatis.executor.parameter.ParameterHandler
org.apache.ibatis.executor.resultset.ResultSetHandler
Interceptor接口的实现类还需要添加@Intercepts注解,@Intercepts注解是用于声明一个拦截器的注解,它用于标识一个类是一个MyBatis拦截器,并指定该拦截器要拦截的目标对象和方法。以mybatis-plus实现的Interceptor 为例,拦截的是StatementHandler.class子类入参为Connection.class, Integer.class的prepare()。
执行流程
执行流程主要分:目标对象的包装、调用方法的拦截
2.1 目标对象的包装
1.首先看在org.apache.ibatis.session.Configuration以下几个位置进行包装返回目标对象(如果对象没有在@Intercepts进行配置,则不进行代理)或者代理对象
2.进入interceptorChain.pluginAll,可以看到是循环执行interceptor的plugin(target)进行对象包装,这里涉及到mybatis拦截器的排序,越往后的越先执行。
3.接下来进入interceptor.plugin,接口的默认方法和实现子类的方法差异不大,以MybatisPlusInterceptor为例,也只是进行判断减少代码的执行,具体可看接下来的逻辑
4.Plugin.wrap(target, this)就是具体的包装逻辑,重点内容来了。首先看下Plugin的类结构,看到它实现了InvocationHandler接口,通过jdk动态代理的方式生成代理对象,剩下的几个函数也很简单
private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
this.target = target;
this.interceptor = interceptor;
this.signatureMap = signatureMap;
}
public static Object wrap(Object target, Interceptor interceptor) {
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
//如果interfaces.length 大于0,则构建Plugin对象并且生成返回代理对象,否则返回目标对象
//MybatisPlusInterceptor不重写该方法的话返回的结果不变,所以说减少逻辑执行
if (interfaces.length > 0) {
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
//遍历@Intercepts中@Signature列表的配置,返回type:method的map数据结构
private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
// issue #251
if (interceptsAnnotation == null) {
throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
}
Signature[] sigs = interceptsAnnotation.value();
Map<Class<?>, Set<Method>> signatureMap = new HashMap<>();
for (Signature sig : sigs) {
//
Set<Method> methods = MapUtil.computeIfAbsent(signatureMap, sig.type(), k -> new HashSet<>());
try {
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;
}
//获取目标对象的实现的接口列表中命中@Signature中type的数组
private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
Set<Class<?>> interfaces = new HashSet<>();
while (type != null) {
for (Class<?> c : type.getInterfaces()) {
if (signatureMap.containsKey(c)) {
interfaces.add(c);
}
}
type = type.getSuperclass();
}
return interfaces.toArray(new Class<?>[0]);
}
5.到这里返回代理对象的逻辑就结束了。
2.2 调用方法的拦截
1.刚才我们看到生成代理对象的逻辑,拦截方法进行功能加强主要放在Plugin对象的Object invoke(Object proxy, Method method, Object[] args) ,我们留意一下下Plugin的属性和构造方法,再看下invoke方法。
2.接下来看下invoke的逻辑
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
//获取接口在映射中的value值,如果拦截的方法在@Signature中有声明,那么调用mybatis拦截器的intercept(),这里的target可能是初始的目标对象也可能是被包装的代理对象,递归返回
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
if (methods != null && methods.contains(method)) {
return interceptor.intercept(new Invocation(target, method, args));
}
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
3.interceptor逻辑看具体的实现子类,mybatis-plus的MybatisPlusInterceptor下篇文章再讲了。