mybaits原始碼分析--binding模組(五)

童話述說我的結局發表於2021-09-06

一、binding模組

接下來我們看看在org.apache.ibatis.binding包下提供的Binding模組 ,binding其實在執行sqlSession.getMapper(UserMapper.class);獲取介面代理的物件時有用到;


發現這個包裡面提供的工具比較少,就幾個,先來分別瞭解下他們的作用,然後在串聯起來。

1.1 MapperRegistry

這顯然是一個註冊中心,這個註冊中是用來儲存MapperProxyFactory物件的,所以這個註冊器中提供的功能肯定是圍繞MapperProxyFactory的新增和獲取操作,來看看具體的程式碼邏輯
成員變數:

 private final Configuration config;
  // 記錄 Mapper 介面和 MapperProxyFactory 之間的關係
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();

addMapper方法

  public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) { // 檢測 type 是否為介面
      if (hasMapper(type)) { // 檢測是否已經加裝過該介面
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        // !Map<Class<?>, MapperProxyFactory<?>> 存放的是介面型別,和對應的工廠類的關係
        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.

        // 註冊了介面之後,根據介面,開始解析所有方法上的註解,例如 @Select >>
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

getMapper方法

  /**
   * 獲取Mapper介面對應的代理物件
   */
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    // 獲取Mapper介面對應的 MapperProxyFactory 物件
    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);
    }
  }

通過這個方法本質上獲取的就是Mapper介面的代理物件。

1.2 MapperProxyFactory

MapperProxyFactory是一個工廠物件,專門負責建立MapperProxy物件。其中核心欄位的含義和功能如下:

/**
 * 負責建立 MapperProxy 物件
 * @author Lasse Voss
 */
public class MapperProxyFactory<T> {

  /**
   * MapperProxyFactory 可以建立 mapperInterface 介面的代理物件
   *     建立的代理物件要實現的介面
   */
  private final Class<T> mapperInterface;
  // 快取
  private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();

  public MapperProxyFactory(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  public Class<T> getMapperInterface() {
    return mapperInterface;
  }

  public Map<Method, MapperMethodInvoker> getMethodCache() {
    return methodCache;
  }

  @SuppressWarnings("unchecked")
  /**
   * 建立實現了 mapperInterface 介面的代理物件
   */
  protected T newInstance(MapperProxy<T> mapperProxy) {
    // 1:類載入器:2:被代理類實現的介面、3:實現了 InvocationHandler 的觸發管理類
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

}

1.3 MapperProxy

通過MapperProxyFactory建立的MapperProxy是Mapper介面的代理物件,實現了InvocationHandler介面,通過前面講解的動態代理模式,那麼這部分的內容就很簡單了。

/**
 * Mapper 代理物件
 * @author  
 * @author  
 */
public class MapperProxy<T> implements InvocationHandler, Serializable {

  private static final long serialVersionUID = -4724728412955527868L;
  private static final int ALLOWED_MODES = MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
      | MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC;
  private static final Constructor<Lookup> lookupConstructor;
  private static final Method privateLookupInMethod;
  private final SqlSession sqlSession; // 記錄關聯的 SqlSession物件
  private final Class<T> mapperInterface; // Mapper介面對應的Class物件
  // 用於快取MapperMethod物件,key是Mapper介面方法對應的Method物件,value是對應的MapperMethod物件。‘
  // MapperMethod物件會完成引數轉換以及SQL語句的執行
  // 注意:MapperMethod中並不會記錄任何狀態資訊,可以在多執行緒間共享
  private final Map<Method, MapperMethodInvoker> methodCache;

  public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethodInvoker> methodCache) {
    this.sqlSession = sqlSession;
    this.mapperInterface = mapperInterface;
    this.methodCache = methodCache;
  }

  static {
    Method privateLookupIn;
    try {
      privateLookupIn = MethodHandles.class.getMethod("privateLookupIn", Class.class, MethodHandles.Lookup.class);
    } catch (NoSuchMethodException e) {
      privateLookupIn = null;
    }
    privateLookupInMethod = privateLookupIn;

    Constructor<Lookup> lookup = null;
    if (privateLookupInMethod == null) {
      // JDK 1.8
      try {
        lookup = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class);
        lookup.setAccessible(true);
      } catch (NoSuchMethodException e) {
        throw new IllegalStateException(
            "There is neither 'privateLookupIn(Class, Lookup)' nor 'Lookup(Class, int)' method in java.lang.invoke.MethodHandles.",
            e);
      } catch (Exception e) {
        lookup = null;
      }
    }
    lookupConstructor = lookup;
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      // toString hashCode equals getClass等方法,無需走到執行SQL的流程
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else {
        // 提升獲取 mapperMethod 的效率,到 MapperMethodInvoker(內部介面) 的 invoke
        // 普通方法會走到 PlainMethodInvoker(內部類) 的 invoke
        return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }

  private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
    try {
      // Java8 中 Map 的方法,根據 key 獲取值,如果值是 null,則把後面Object 的值賦給 key
      // 如果獲取不到,就建立
      // 獲取的是 MapperMethodInvoker(介面) 物件,只有一個invoke方法
      // 根據method 去methodCache中獲取 如果返回空 則用第二個引數填充
      return methodCache.computeIfAbsent(method, m -> {
        if (m.isDefault()) {
          // 介面的預設方法(Java8),只要實現介面都會繼承介面的預設方法,例如 List.sort()
          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 {
          // 建立了一個 MapperMethod
          return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
        }
      });
    } catch (RuntimeException re) {
      Throwable cause = re.getCause();
      throw cause == null ? re : cause;
    }
  }

  private MethodHandle getMethodHandleJava9(Method method)
      throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
    final Class<?> declaringClass = method.getDeclaringClass();
    return ((Lookup) privateLookupInMethod.invoke(null, declaringClass, MethodHandles.lookup())).findSpecial(
        declaringClass, method.getName(), MethodType.methodType(method.getReturnType(), method.getParameterTypes()),
        declaringClass);
  }

  private MethodHandle getMethodHandleJava8(Method method)
      throws IllegalAccessException, InstantiationException, InvocationTargetException {
    final Class<?> declaringClass = method.getDeclaringClass();
    return lookupConstructor.newInstance(declaringClass, ALLOWED_MODES).unreflectSpecial(method, declaringClass);
  }

  interface MapperMethodInvoker {
    Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable;
  }

  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 {
      // SQL執行的真正起點
      return mapperMethod.execute(sqlSession, args);
    }
  }

  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);
    }
  }
}

1.4 MapperMethod

MapperMethod中封裝了Mapper介面中對應方法的資訊,以及SQL語句的資訊,可以把MapperMethod看成是配置檔案中定義的SQL語句和Mapper介面的橋樑。

 

 屬性和構造方法

  // statement id (例如:com.gupaoedu.mapper.BlogMapper.selectBlogById) 和 SQL 型別
  private final SqlCommand command;
  // 方法簽名,主要是返回值的型別
  private final MethodSignature method;

  public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    this.command = new SqlCommand(config, mapperInterface, method);
    this.method = new MethodSignature(config, mapperInterface, method);
  }

1.4.1 SqlCommand

SqlCommand是MapperMethod中定義的內部類,記錄了SQL語句名稱以及對應的型別(UNKNOWN,INSERT,UPDATE,DELETE,SELECT,FLUSH)

 

1.4.2 MethodSignature

MethodSignature也是MapperMethod的內部類,在其中封裝了Mapper介面中定義的方法相關資訊。

  private final boolean returnsMany; // 判斷返回是否為 Collection型別或者陣列型別
    private final boolean returnsMap; // 返回值是否為 Map型別
    private final boolean returnsVoid; // 返回值型別是否為 void
    private final boolean returnsCursor; // 返回值型別是否為 Cursor 型別
    private final boolean returnsOptional; // 返回值型別是否為 Optional 型別
    private final Class<?> returnType; // 返回值型別
    private final String mapKey; // 如果返回值型別為 Map  則 mapKey 記錄了作為 key的 列名
    private final Integer resultHandlerIndex; // 用來標記該方法引數列表中 ResultHandler 型別引數的位置
    private final Integer rowBoundsIndex; // 用來標記該方法引數列表中 rowBounds 型別引數的位置
    private final ParamNameResolver paramNameResolver; // 該方法對應的 ParamNameResolver 物件

構造方法中完成了相關資訊分初始化操作

  /**
     * 方法簽名
     * @param configuration
     * @param mapperInterface
     * @param method
     */
    public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {
      // 獲取介面方法的返回型別
      Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
      if (resolvedReturnType instanceof Class<?>) {
        this.returnType = (Class<?>) resolvedReturnType;
      } else if (resolvedReturnType instanceof ParameterizedType) {
        this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType();
      } else {
        this.returnType = method.getReturnType();
      }
      this.returnsVoid = void.class.equals(this.returnType);
      this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();
      this.returnsCursor = Cursor.class.equals(this.returnType);
      this.returnsOptional = Optional.class.equals(this.returnType);
      this.mapKey = getMapKey(method);
      this.returnsMap = this.mapKey != null;
      // getUniqueParamIndex 查詢指定型別的引數在 引數列表中的位置
      this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
      this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
      this.paramNameResolver = new ParamNameResolver(configuration, method);
    }

getUniqueParamIndex的主要作用是 查詢指定型別的引數在引數列表中的位置

    /**
     * 查詢指定型別的引數在引數列表中的位置
     * @param method
     * @param paramType
     * @return
     */
    private Integer getUniqueParamIndex(Method method, Class<?> paramType) {
      Integer index = null;
      // 獲取對應方法的引數列表
      final Class<?>[] argTypes = method.getParameterTypes();
      // 遍歷
      for (int i = 0; i < argTypes.length; i++) {
        // 判斷是否是需要查詢的型別
        if (paramType.isAssignableFrom(argTypes[i])) {
          // 記錄對應型別在引數列表中的位置
          if (index == null) {
            index = i;
          } else {
            // RowBounds 和 ResultHandler 型別的引數只能有一個,不能重複出現
            throw new BindingException(method.getName() + " cannot have multiple " + paramType.getSimpleName() + " parameters");
          }
        }
      }
      return index;
    }

1.4.3 execute方法

最後來看下再MapperMethod中最核心的方法execute方法,這個方法完成了資料庫操作

  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) { // 根據SQL語句的型別呼叫SqlSession對應的方法
      case INSERT: {
        // 通過 ParamNameResolver 處理args[] 陣列 將使用者傳入的實參和指定引數名稱關聯起來
        Object param = method.convertArgsToSqlCommandParam(args);
        // sqlSession.insert(command.getName(), param) 呼叫SqlSession的insert方法
        // rowCountResult 方法會根據 method 欄位中記錄的方法的返回值型別對結果進行轉換
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT:
        if (method.returnsVoid() && method.hasResultHandler()) {
          // 返回值為空 且 ResultSet通過 ResultHandler處理的方法
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {
          result = executeForCursor(sqlSession, args);
        } else {
          // 返回值為 單一物件的方法
          Object param = method.convertArgsToSqlCommandParam(args);
          // 普通 select 語句的執行入口 >>
          result = sqlSession.selectOne(command.getName(), param);
          if (method.returnsOptional()
              && (result == null || !method.getReturnType().equals(result.getClass()))) {
            result = Optional.ofNullable(result);
          }
        }
        break;
      case FLUSH:
        result = sqlSession.flushStatements();
        break;
      default:
        throw new BindingException("Unknown execution method for: " + command.getName());
    }
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
      throw new BindingException("Mapper method '" + command.getName()
          + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
  }

在這個方法中所對應一些分支方法都還是比較簡單的,看一下其實就懂,我就不想再往裡跟了

1.4.4 核心流程串聯

首先在對映檔案載入解析的位置,XMLMapperBuilder.parse位置

  public void parse() {
    // 總體上做了兩件事情,對於語句的註冊和介面的註冊
    if (!configuration.isResourceLoaded(resource)) {
      // 1、具體增刪改查標籤的解析。
      // 一個標籤一個MappedStatement。 >>
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      // 2、把namespace(介面型別)和工廠類繫結起來,放到一個map。
      // 一個namespace 一個 MapperProxyFactory >>
      bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }

在bindMapperForNamespace中會完成Mapper介面的註冊並呼叫前面介紹過的addMapper方法然後就是在我們執行

// 4.通過SqlSession中提供的 API方法來運算元據庫
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> list = mapper.selectUserList();

這兩行程式碼的內部邏輯,首先看下getMapper方法

 

 public <T> T getMapper(Class<T> type) {
    return configuration.getMapper(type, this);
  }
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    // mapperRegistry中註冊的有Mapper的相關資訊 在解析對映檔案時 呼叫過addMapper方法
    return mapperRegistry.getMapper(type, sqlSession);
  }

然後就是從MapperRegistry中獲取對應的MapperProxyFactory物件。

  /**
   * 獲取Mapper介面對應的代理物件
   */
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    // 獲取Mapper介面對應的 MapperProxyFactory 物件
    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);
    }
  }

然後根據MapperProxyFactory物件獲取Mapper介面對應的代理物件。

  /**
   * 建立實現了 mapperInterface 介面的代理物件
   */
  protected T newInstance(MapperProxy<T> mapperProxy) {
    // 1:類載入器:2:被代理類實現的介面、3:實現了 InvocationHandler 的觸發管理類
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

不清楚可以看前面畫的時序圖

 

 然後我們再來看下呼叫代理物件中的方法執行的順序

List<User> list = mapper.selectUserList();

會進入MapperProxy的Invoker方法中

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      // toString hashCode equals getClass等方法,無需走到執行SQL的流程
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else {
        // 提升獲取 mapperMethod 的效率,到 MapperMethodInvoker(內部介面) 的 invoke
        // 普通方法會走到 PlainMethodInvoker(內部類) 的 invoke
        return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }

然後進入PlainMethodInvoker中的invoke方法

    @Override
    public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
      // SQL執行的真正起點
      return mapperMethod.execute(sqlSession, args);
    }
  }

然後會進入到 MapperMethod的execute方法中

 public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) { // 根據SQL語句的型別呼叫SqlSession對應的方法
      case INSERT: {
        // 通過 ParamNameResolver 處理args[] 陣列 將使用者傳入的實參和指定引數名稱關聯起來
        Object param = method.convertArgsToSqlCommandParam(args);
        // sqlSession.insert(command.getName(), param) 呼叫SqlSession的insert方法
        // rowCountResult 方法會根據 method 欄位中記錄的方法的返回值型別對結果進行轉換
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT:
        if (method.returnsVoid() && method.hasResultHandler()) {
          // 返回值為空 且 ResultSet通過 ResultHandler處理的方法
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {
          result = executeForCursor(sqlSession, args);
        } else {
          // 返回值為 單一物件的方法
          Object param = method.convertArgsToSqlCommandParam(args);
          // 普通 select 語句的執行入口 >>
          result = sqlSession.selectOne(command.getName(), param);
          if (method.returnsOptional()
              && (result == null || !method.getReturnType().equals(result.getClass()))) {
            result = Optional.ofNullable(result);
          }
        }
        break;
      case FLUSH:
        result = sqlSession.flushStatements();
        break;
      default:
        throw new BindingException("Unknown execution method for: " + command.getName());
    }
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
      throw new BindingException("Mapper method '" + command.getName()
          + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
  }

之後就會根據對應的SQL型別而呼叫SqlSession中對應的方法來執行操作

相關文章