Talk about the binding module of Mybatis

Time:2022-11-25

Talk about the binding module of Mybatis

Why do we only need to write interfaces and xml files to execute sql when using Mybatis? This is what the binding module of Mybatis needs to do. Today we analyze the binding module of Mybatis. The classes under the binding package mainly include four MapperRegistry, MapperProxyFactory, MapperProxy and MapperMethod
Mapping registration class MapperRegistry

MapperRegistry is a registration class, and its knownMappers collection stores the mapping relationship between the Mapper interface and the MapperProxyFactory instance

Its addMapper() method is the method to add the mapping relationship:

public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        knownMappers.put(type, new MapperProxyFactory<>(type));
        // It's important that the type is added before the parser is run
        // otherwise the binding may automatically be attempted by the
        // mapper parser. If the type is already known, it won't try.
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

When the incoming type is an interface and there is no knownMappers, put the type and the corresponding MapperProxyFactory instance into knownMappers

Then when executing sql, Mybatis will call the MapperRegistry.getMapper() method

@SuppressWarnings("unchecked")
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }
First find the corresponding MapperProxyFactory instance from the knownMappers collection

Then call the newInstance() method
Mapping proxy factory class MapperProxyFactory

The newInstance() method of MapperProxyFactory:
public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }
  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

Generate an instance of the proxy object of the mapperInterface interface. The proxy class is MapperProxy, which implements InvocationHandler, and uses jdk dynamic proxy to generate proxy objects

Take a look at the invoke() method it overrides:
Mapping proxy class MapperProxy

The invoke() method of MapperProxy:

@Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else {
        return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }

Classes that are not Object will be intercepted, and the cachedInvoker() method will be intercepted, and the invoke() method will be executed by others.

private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
    try {
      return MapUtil.computeIfAbsent(methodCache, method, m -> {
        if (m.isDefault()) {
          try {
            if (privateLookupInMethod == null) {
              return new DefaultMethodInvoker(getMethodHandleJava8(method));
            } else {
              return new DefaultMethodInvoker(getMethodHandleJava9(method));
            }
          } catch (IllegalAccessException | InstantiationException | InvocationTargetException
              | NoSuchMethodException e) {
            throw new RuntimeException(e);
          }
        } else {
          return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
        }
      });
    } catch (RuntimeException re) {
      Throwable cause = re.getCause();
      throw cause == null ? re : cause;
    }
  }
Get the corresponding MapperMethodInvoker from methodCache

If there is no cache, if the method is the default method, create a DefaultMethodInvoker object, otherwise create a PlainMethodInvoker object
The default method invokes the class DefaultMethodInvoker

For the DefaultMethodInvoker class, the call is completed through the MethodHandle
private static class DefaultMethodInvoker implements MapperMethodInvoker {
    private final MethodHandle methodHandle;

    public DefaultMethodInvoker(MethodHandle methodHandle) {
      super();
      this.methodHandle = methodHandle;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
      return methodHandle.bindTo(proxy).invokeWithArguments(args);
    }
  }

For the class PlainMethodInvoker corresponding to the ordinary method, the call is completed through MapperMethod.

private static class PlainMethodInvoker implements MapperMethodInvoker {
    private final MapperMethod mapperMethod;

    public PlainMethodInvoker(MapperMethod mapperMethod) {
      super();
      this.mapperMethod = mapperMethod;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
      return mapperMethod.execute(sqlSession, args);
    }
  }

Summarize

This article mainly introduces the binding module of Mybatis. Its MapperRegistry stores the mapping relationship between the Mapper interface and the MapperProxyFactory instance. MapperProxyFactory is a factory that generates the proxy class of the Mapper interface. MapperProxy implements InvocationHandler and rewrites invoke () method for interception processing, according to the method to determine whether it is a default type to create a different MethodInvoker class, and then call MapperMethod to execute sql, the next article we will introduce the MapperMethod class