1. 程式人生 > >Mybatis 原始碼分析:獲取 Mapper 介面物件

Mybatis 原始碼分析:獲取 Mapper 介面物件

我們知道使用 mybatis 作為 ORM 框架時,想要使用面向介面的方式操作資料庫,即使用 mapper 檔案形式,那麼就需要獲取 Mapper 介面物件,從而才能對資料庫進行操作。那麼問題來了,在 java 中是不可能對 interface 進行 new 的,那麼 mybatis 是怎麼做到面向 Mapper 介面的呢?那就從原始碼的角度揭開這層其實沒有想象那麼高深的面紗!
首先來看看 SqlSession(預設實現 DefaultSqlSession) 類的 getMapper() 方法。

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

非常簡單,它把獲取 mapper 介面的物件邏輯委託給了 Configuration 類的 getMapper() 方法。

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

這看起來似乎有那麼點意思了,它從 mapperRegistry 中去獲取 mapper 介面物件,並且還把 SqlSession 傳遞過去。看過我之前的文章應該清楚 mapperRegistry 是什麼東西,從字面意思來看,它就是 Mapper 註冊中心,它的定義是:

protected final MapperRegistry mapperRegistry = new MapperRegistry(this);

那麼接下來應該是到了揭開面紗的門口了,我們看看 MapperRegistry 類的 getMapper() 方法又幹了啥呢?

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 獲取 Mapper 介面對應的 MapperProxyFactory 物件,那 knownMappers 又是什麼呢,在之前的文章有說到,它就是在構建 SqlSessionFactory 物件時,對 xml 配置檔案解析後把所有的 mapper 介面都會放入在 knownMappers 這個 HashMap 中去,key 為 mapper 介面的全限定名,value 為 MapperProxyFactory 物件。knownMappers 欄位的定義為:

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

然後,會使用 MapperProxyFactory 類的 newInstance() 方法,此時就是揭開面紗見證奇蹟的時刻到了,看看這個方法到底幹了啥

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

這裡首先生成了一個 MapperProxy 物件,那這個物件又是啥呢,字面意思看起來是 Mapper 的代理物件,這個稍等說。方法之後又掉用了一個過載的 newInstance() 方法:

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

千呼萬喚始出來,終於見到了最核心的一行程式碼,它使用了 jdk 自帶的動態代理機制對 Mapper 介面生成了一個代理物件,在這裡可以看到 MapperProxy 是作為 Proxy.newProxyInstance() 方法的第三個引數傳遞進去的,所以可以想象到 MapperProxy 肯定實現了 jdk 的 InvocationHandler 介面。

public class MapperProxy<T> implements InvocationHandler, Serializable {
  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;
  }

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

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

現在,我們就知道了獲取 Mapper 介面物件的整個流程,至於之後對其進行方法呼叫的過程,且看下回分解~~~
最後,老規矩附上整個過程的時序圖:
獲取 mapper 介面物件