The realization principle and optimization of Mybatis plug-in

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);
  }
}
  1. Load the interceptor configured in xml during initialization and add it to the interceptors collection of InterceptorChain,
  2. In creation ParameterHandler, ResultSetHandler, StatementHandler, Executorcalled when an object pluginAll(Object target)method, traversing the Interceptorexecution org.apache.ibatis.plugin.Interceptor#plugin,
  3. This method is called internal org.apache.ibatis.plugin.Plugin#wrap(Object target, Interceptor interceptor)analytic Interceptordefined on @Interceptsand @Signatureannotations to parse methods need to be intercepted, their JDK dynamic proxy, the proxy class with the java.lang.reflect.InvocationHandlerrealization that is org.apache.ibatis.plugin.Plugin, targetas a member variable Plugin preservation.
  4. 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.
  5. 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 Interceptorinterception of even the same interface ( ParameterHandler, ResultSetHandler, StatementHandler, Executorone), 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)
                            ......
  1. 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
  2. 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;
    }
}

Guess you like

Origin blog.csdn.net/u013202238/article/details/107743698