天天看點

MyBatis原理分析之擷取Mapper接口的代理對象

流程原理分析系列:

MyBatis原理分析之擷取SqlSessionFactor

MyBatis原理分析之擷取SqlSession

MyBatis原理分析之擷取Mapper接口的代理對象

MyBatis原理分析之查詢單個對象

本篇博文是原理分析的第三篇。

當使用mapper接口進行CRUD時,其實是其代理對象在發揮作用,SQLsession擷取mapper接口的代理對象時序圖如下:

MyBatis原理分析之擷取Mapper接口的代理對象

【1】DefaultSqlSession

如下代碼所示,這裡其實是調用了configuration執行個體的方法。該方法是一個泛型方法,參數有​

​Class<T> type​

​​表示你的接口Class對象,比如​

​UserMapper.class---interface com.jane.mapper.UserMapper​

@Override
  public <T> T getMapper(Class<T> type) {
    return configuration.<T>getMapper(type, this);
  }      

關于DefaultSqlSession更多詳情參考博文:MyBatis中SqlSessionFactory和SqlSession簡解

【2】Configuration

configuration有常量成員​

​protected final MapperRegistry mapperRegistry = new MapperRegistry(this);​

​執行個體,mapperRegistry 引用了目前configuration。

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }      

MapperRegistry屬性和構造方法如下:

public class MapperRegistry {

  private final Configuration config;
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();

  public MapperRegistry(Configuration config) {
    this.config = config;
  }
//..
}      

在MyBatis原理分析之擷取SqlSessionFactory一文中我們可以得知建立SqlSessionFactory時對所有的mapper(xml和接口)進行了解析并為每一個mapper接口建立了MapperProxyFactory對象放入knownMappers 中。

MapperRegistry的addMapper方法如下:

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放入目前解析的mapper對應的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.
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }      

【3】MapperRegistry

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

代碼解釋如下:

  • ① 從knownMappers中擷取目前class對象的mapperProxyFactory執行個體;
    MyBatis原理分析之擷取Mapper接口的代理對象
  • ② 如果不存在則抛出異常;
  • ③ 如果存在則執行​

    ​mapperProxyFactory.newInstance(sqlSession)​

    ​​來擷取目前​

    ​mapper​

    ​的代理對象

【4】MapperProxyFactory

MapperProxyFactory主要屬性和構造方法

public class MapperProxyFactory<T> {

  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();

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

可以看到其除了接口的class對象外還維護了一個私有ConcurrentHashMap類型常量​

​methodCache​

​。

建立執行個體對象MapperProxy

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

代碼解釋如下:

  • ① 根據​

    ​sqlsession​

    ​​、​

    ​mapperInterface​

    ​​(接口的class對象),以及類型的​

    ​methodCache​

    ​​建立​

    ​MapperProxy​

  • ② 為Mapper建立代理對象

這裡需要注意的是​

​MapperProxy​

​​是一個​

​InvocationHandler​

​​類型,需要實作​

​Object invoke(Object proxy, Method method, Object[] args)​

​方法。

MapperProxy的主要屬性和構造方法

public class MapperProxy<T> implements InvocationHandler, Serializable {

  private static final long serialVersionUID = -6424540398559729838L;
  private final SqlSession sqlSession;
  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache;

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

InvocationHandler是什麼呢?InvocationHandler是由代理執行個體的調用處理程式實作的接口。也就是說一個類/接口的代理執行個體的調用處理程式必須實作InvocationHandler接口的invoke方法。可以了解為本文中MapperProxy就是Mapper代理執行個體的調用處理程式。

InvocationHandler的invoke方法如下:

Object invoke(Object proxy, Method method, Object[] args)
proxy:代理執行個體對象
method:目标方法
args:方法入參      

Proxy是什麼?Proxy是專門完成代理的操作類,是所有動态代理類的父類,通過此類為一個或多個接口動态地生成實作類。使用 Proxy 生成一個動态代理時,往往并不會憑空産生一個動态代理,這樣沒有太大的意義。通常都是為指定的目标對象生成動态代理

//直接建立一個動态代理對象
static Object newProxyInstance( ClassLoader loader, Class<?>[] interfaces,InvocationHandler h ) 
loader :定義代理類的類加載器
interfaces:被代理類實作的所有接口
h:代理執行個體的調用處理程式
該方法将會傳回一個代理對象,代理對象有代理調用處理程式--InvocationHandler      

根據mapperProxy建立Mapper的代理對象

@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
  return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}      

可以看下最終得到的Mapper的代理對象如下(​

​h 表示其是一個InvocationHandler也就是調用處理程式​

​​):

MyBatis原理分析之擷取Mapper接口的代理對象

每個代理執行個體都有一個關聯的調用處理程式InvocationHandler。在代理執行個體上調用方法時,方法調用将被編碼并發送到其調用處理程式的​​

​invoke​

​方法。

關于java的動态代理更多參考博文:

​Java中的代理模式與動(靜)态代理Java中動态代理使用與原理詳解

【5】MapperMethod

上面我們提到了​

​MapperProxyFactory​

​​有常量成員​

​methodCache​

​ ,在類加載過程中就進行了初始化。

private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();      

根據​

​MapperProxyFactory​

​​生成​

​MapperProxy​

​​執行個體時,将​

​ConcurrentHashMap​

​類型的methodCache 傳了過去。

final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);      

​MapperProxy​

​​實作了​

​InvocationHandler​

​​接口的​

​invoke​

​​方法,那麼在使用​

​Mapper​

​​進行​

​CRUD​

​​時實際會調用對應的​

​MapperProxy​

​​的​

​invoke​

​方法:

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

如上代碼可以看到其先擷取了MapperMethod 執行個體,然後調用了MapperMethod 執行個體的execute方法。

cachedMapperMethod方法

private MapperMethod cachedMapperMethod(Method method) {
    MapperMethod mapperMethod = methodCache.get(method);
    if (mapperMethod == null) {
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }      

代碼解釋如下:

  • ① 嘗試從​

    ​ConcurrentHashMap​

    ​​類型的​

    ​methodCache​

    ​​擷取目前​

    ​method​

    ​​對應的​

    ​MapperMethod​

  • ② 如果①沒有擷取到,則建立​

    ​MapperMethod​

    ​執行個體
  • ③ 将​

    ​{method=mapperMethod}​

    ​​放入​

    ​methodCache​

    ​中
  • ④ 傳回MapperMethod 執行個體

這裡先看一下一個Method對象是個什麼?

MyBatis原理分析之擷取Mapper接口的代理對象

​new MapperMethod(mapperInterface, method, sqlSession.getConfiguration())​

構造方法如下:

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

這裡我們可以看到,其建立了SqlCommand和MethodSignature執行個體。

MapperMethod的UML圖如下所示

MyBatis原理分析之擷取Mapper接口的代理對象

再看一下MapperMethod執行個體對象

MyBatis原理分析之擷取Mapper接口的代理對象

​new SqlCommand(config, mapperInterface, method)​

SqlCommand是MapperMethod靜态嵌套類,主要屬性是name和SqlCommandType。

public static class SqlCommand {

    private final String name;
    private final SqlCommandType type;

    public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
      String statementName = mapperInterface.getName() + "." + method.getName();
      MappedStatement ms = null;
      if (configuration.hasStatement(statementName)) {
        ms = configuration.getMappedStatement(statementName);
      } else if (!mapperInterface.equals(method.getDeclaringClass())) { // issue #35
        String parentStatementName = method.getDeclaringClass().getName() + "." + method.getName();
        if (configuration.hasStatement(parentStatementName)) {
          ms = configuration.getMappedStatement(parentStatementName);
        }
      }
      if (ms == null) {
        if(method.getAnnotation(Flush.class) != null){
          name = null;
          type = SqlCommandType.FLUSH;
        } else {
          throw new BindingException("Invalid bound statement (not found): " + statementName);
        }
      } else {
        name = ms.getId();
        type = ms.getSqlCommandType();
        if (type == SqlCommandType.UNKNOWN) {
          throw new BindingException("Unknown execution method for: " + name);
        }
      }
    }
//...
  }      

構造方法如下解釋如下

  • ①​

    ​mapperInterface.getName() + "." + method.getName()​

    ​​解析擷取到​

    ​statementName​

    ​​ ,如​

    ​com.mybatis.dao.EmployeeMapper.getEmpById​

    ​;
  • ② 判斷configuration執行個體的​

    ​Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")​

    ​成員對象中是否有①中的statementName;
  • ③ 如果存在,則擷取statementName對應的MappedStatement;
  • ④ 如果不存在且目前接口Class不是方法的所屬Class,則根據方法的所屬Class的name與方法名字解析新的statementName
  • ⑤ 如果configuration的​

    ​Map<String, MappedStatement> mappedStatements​

    ​成員中存在新的statementName,則傳回對應的MappedStatement
  • ⑥ 如果最終得到的MappedStatement為null,則判斷方法上面是否有注解​

    ​@Flush​

    ​;
  • ⑦ 如果有注解​

    ​@Flush​

    ​​,則指派​

    ​name=null,type = SqlCommandType.FLUSH​

    ​;
  • ⑧ 如果沒有注解​

    ​@Flush​

    ​,則抛出異常
  • ⑨ 如果最終得到的MappedStatement不為null,則指派​

    ​name = ms.getId(); type = ms.getSqlCommandType();​

    ​。
  • ⑩ 如果type為UNKNOWN,則抛出異常

SqlCommandType 是一個枚舉類,主要有值​

​UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH​

public enum SqlCommandType {
  UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH;
}      

hasStatement(statementName)與getMappedStatement(statementName)

configuration.hasStatement(statementName)) ;
configuration.getMappedStatement(statementName);      

上面我們看到這樣兩句代碼,代碼表面本身很好了解,我們跟進去看:

hasStatement代碼流程片段如下:

public boolean hasStatement(String statementName) {
   return hasStatement(statementName, true);
 }
public boolean hasStatement(String statementName, boolean validateIncompleteStatements) {
   if (validateIncompleteStatements) {
     buildAllStatements();
   }
   return mappedStatements.containsKey(statementName);
 }      

getMappedStatement代碼流程片段如下:

public MappedStatement getMappedStatement(String id) {
  return this.getMappedStatement(id, true);
}

public MappedStatement getMappedStatement(String id, boolean validateIncompleteStatements) {
  if (validateIncompleteStatements) {
    buildAllStatements();
  }
  return mappedStatements.get(id);
}      

​getMappedStatement​

​​方法在以下地方有被調用

MyBatis原理分析之擷取Mapper接口的代理對象

可以看到,其都調用了​

​buildAllStatements();​

​​。那麼這個方法是什麼呢?如下代碼所示,其實就是mybatis提供的​

​快速失敗​

​機制

protected void buildAllStatements() {
    parsePendingResultMaps();
    if (!incompleteCacheRefs.isEmpty()) {
      synchronized (incompleteCacheRefs) {
        incompleteCacheRefs.removeIf(x -> x.resolveCacheRef() != null);
      }
    }
    if (!incompleteStatements.isEmpty()) {
      synchronized (incompleteStatements) {
        incompleteStatements.removeIf(x -> {
          x.parseStatementNode();
          return true;
        });
      }
    }
    if (!incompleteMethods.isEmpty()) {
      synchronized (incompleteMethods) {
        incompleteMethods.removeIf(x -> {
          x.resolve();
          return true;
        });
      }
    }
  }      

在該方法上面有如下注釋:

Parses all the unprocessed statement nodes in the cache. It is recommended to call this method once all the mappers are added as it provides fail-fast statement validation.

解析緩存中所有未處理的statement節點。建議在添加所有映射程式後調用此方法,因為它提供​

​fail fast​

​語句驗證。

resolveCacheRef、parseStatementNode、resolve都會抛出異常