Principio de implementación del complemento Mybatis
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);
}
}
- Cargue el interceptor configurado en xml durante la inicialización y agréguelo a la colección de interceptores de InterceptorChain,
- En la creación
ParameterHandler
,ResultSetHandler
,StatementHandler
,Executor
llamada cuando un objetopluginAll(Object target)
método, que atraviesa laInterceptor
ejecuciónorg.apache.ibatis.plugin.Interceptor#plugin
, - Este método se llama
org.apache.ibatis.plugin.Plugin#wrap(Object target, Interceptor interceptor)
analítico internoInterceptor
definido en@Intercepts
y las@Signature
anotaciones para analizar los métodos deben ser interceptadas, su proxy dinámico JDK, la clase de proxy con lajava.lang.reflect.InvocationHandler
realizaciónorg.apache.ibatis.plugin.Plugin
, es decir ,target
como una preservación del complemento de variable miembro. - Al atravesar la colección de interceptores para crear una clase de proxy, el proxy de destino devuelto por el Interceptor anterior se utilizará como la clase de destino del siguiente Interceptor.
- En el momento de la ejecución, se juzga si es necesario ejecutar el método interceptado
interceptor
y, si es necesario , se ejecuta. Si no lo necesita, llame directamente al método de la clase de destino objetivo.
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);
}
}
......
}
Inconvenientes de implementación del complemento Mybatis
En la llamada del ciclo interceptor.plugin(target);
, si hay múltiples Interceptor
interceptación de incluso la misma interfaz ( ParameterHandler
, ResultSetHandler
, StatementHandler
, Executor
uno), porque cada vez que se la un pase target = interceptor.plugin(target)
volvió objeto, dará lugar a la clase objetivo es el agente varias veces. Esto en realidad forma una cadena de interceptores proxy dinámica
ProxyA(Hnadler)
--> ProxyB(Hnadler)
--> ProxyC(Hnadler)
--> ProxyD(Hnadler)
......
- En primer lugar, el rendimiento de la llamada al método de la clase de proxy no es tan eficiente como el método de llamar directamente a la clase de destino, y cuanto más proxy, peor es el rendimiento. @Ver PR
- El proxy de la misma clase varias veces dará como resultado una estructura de instancia de objeto compleja, que no es propicia para analizar la lógica de ejecución del complemento.
El complemento Mybatis realiza la optimización
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;
}
}