Mybatis plug-in implementation principle
org.apache.ibatis.plugin.InterceptorChain
public class InterceptorChain {
private final List<Interceptor> interceptors = new ArrayList<>();
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
public void addInterceptor(Interceptor interceptor) {
interceptors.add(interceptor);
}
public List<Interceptor> getInterceptors() {
return Collections.unmodifiableList(interceptors);
}
}
- Load the interceptor configured in xml during initialization and add it to the interceptors collection of InterceptorChain,
- In creation
ParameterHandler
,ResultSetHandler
,StatementHandler
,Executor
called when an objectpluginAll(Object target)
method, traversing theInterceptor
executionorg.apache.ibatis.plugin.Interceptor#plugin
, - This method is called internal
org.apache.ibatis.plugin.Plugin#wrap(Object target, Interceptor interceptor)
analyticInterceptor
defined on@Intercepts
and@Signature
annotations to parse methods need to be intercepted, their JDK dynamic proxy, the proxy class with thejava.lang.reflect.InvocationHandler
realization that isorg.apache.ibatis.plugin.Plugin
,target
as a member variable Plugin preservation. - When traversing the interceptors collection to create a proxy class, the target proxy returned by the previous Interceptor will be used as the target class of the next Interceptor.
- At the time of execution, it is judged whether the intercepted method needs to be executed
interceptor
, and if necessary , it is executed. If you don't need it, call the method of the target target class directly.
public class Plugin implements InvocationHandler {
private final Object target;
private final Interceptor interceptor;
private final Map<Class<?>, Set<Method>> signatureMap;
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);
if (interfaces.length > 0) {
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
@Override
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)) {
return interceptor.intercept(new Invocation(target, method, args));
}
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
......
}
Mybatis plug-in implementation drawbacks
In the cycle call interceptor.plugin(target);
, if there are multiple Interceptor
interception of even the same interface ( ParameterHandler
, ResultSetHandler
, StatementHandler
, Executor
one), because every time a pass is the target = interceptor.plugin(target)
object returned, will result in the target class is the agent several times. This actually forms a dynamic proxy interceptor chain
ProxyA(Hnadler)
--> ProxyB(Hnadler)
--> ProxyC(Hnadler)
--> ProxyD(Hnadler)
......
- First of all, the performance of the method call of the proxy class is not as efficient as the method of directly calling the target class, and the more the proxy, the worse the performance. @See PR
- Proxying the same class multiple times will result in a complex object instance structure, which is not conducive to analyzing the execution logic of the plug-in.
Mybatis plug-in realizes optimization
org.apache.ibatis.plugin.Plugin
package org.apache.ibatis.plugin;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.ibatis.reflection.ExceptionUtil;
/**
* @author bruce lwl
*/
public class Plugin implements InvocationHandler {
private final Object target;
//private final Interceptor interceptor;
//private final Map<Class<?>, Set<Method>> signatureMap;
private final Map<Method, List<Interceptor>> interceptorMap;
//private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
private Plugin(Object target, Interceptor interceptor, Map<Method, List<Interceptor>> interceptorMap) {
this.target = target;
//this.interceptor = interceptor;
//this.signatureMap = signatureMap;
this.interceptorMap = interceptorMap;
}
public Map<Method, List<Interceptor>> getInterceptorMap() {
return interceptorMap;
}
public static Object wrap(Object target, Interceptor interceptor) {
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
if (Proxy.isProxyClass(target.getClass())) {
InvocationHandler invocationHandler = Proxy.getInvocationHandler(target);
if (invocationHandler instanceof Plugin){
Map<Method, List<Interceptor>> interceptorMap = ((Plugin) invocationHandler).getInterceptorMap();
mapping(interceptor, signatureMap, interceptorMap);
return target;
}
}
Map<Method, List<Interceptor>> interceptorMap = new HashMap<>();
mapping(interceptor, signatureMap, interceptorMap);
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, interceptorMap));
}
return target;
}
/**
* @param interceptor Interceptor实例对象
* @param signatureMap 被拦截的接口有哪些方法是被拦截的
* @param interceptorMap 被拦截的方法,对应哪些Interceptor实例对象
*/
private static void mapping(Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap, Map<Method, List<Interceptor>> interceptorMap) {
for (Set<Method> methods : signatureMap.values()) {
for (Method method : methods) {
interceptorMap.computeIfAbsent(method, (key) -> new ArrayList<>()).add(interceptor);
}
}
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
List<Interceptor> interceptors = interceptorMap.get(method);
if (interceptors != null) {
return new InvocationList(interceptors, target, method, args).proceed();
}
//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);
}
}
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 = signatureMap.computeIfAbsent(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;
}
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<?>[interfaces.size()]);
}
}
InvocationList
package org.apache.ibatis.plugin;
import org.apache.ibatis.reflection.ExceptionUtil;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
public class InvocationList extends Invocation {
private final List<Interceptor> interceptors;
int index = 0;
public InvocationList(List<Interceptor> interceptors, Object target, Method method, Object[] args) {
super(target, method, args);
this.interceptors = interceptors;
index = interceptors.size();
}
@Override
public Object proceed() throws InvocationTargetException, IllegalAccessException {
if ((index--) == 0) {
return super.proceed();
}
Interceptor interceptor = interceptors.get(index);
Object result = null;
try {
result = interceptor.intercept(this);
} catch (Throwable throwable) {
try {
throw ExceptionUtil.unwrapThrowable(throwable);
} catch (Throwable e) {
e.printStackTrace();
}
}
return result;
}
}