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 介面物件的整個流程,至於之後對其進行方法呼叫的過程,且看下回分解~~~
最後,老規矩附上整個過程的時序圖: