上期我們說了Mybatis是怎麼建立會話的Mybatis源碼解析(一)建立會話SqlSession,在建立會話的過程中,會讀取
mybatis
的配置檔案和
mapper.xml
檔案來封裝一些内容,主要有相關的配置資訊,最重要的還是建構了一個map來存儲
MappedStatement
資訊,我們會在這篇文章詳細講解
我們同樣看下面的測試方法:
@Test
public void testFindAll() throws IOException {
//加載主配置檔案,目的是為了建構SqlSessionFactory對象
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
//建立SqlSessionFactory對象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
//通過SqlSessionFactory工廠對象建立SqlSesssion對象
SqlSession sqlSession = factory.openSession();
//通過Session建立UserDao接口代理對象
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.findAll();
for (User user : userList) {
System.out.println(user);
}
}
上一篇我們講解了建立
SqlSession
,也就是下面三句代碼
//加載主配置檔案,目的是為了建構SqlSessionFactory對象
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
//建立SqlSessionFactory對象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
//通過SqlSessionFactory工廠對象建立SqlSesssion對象
SqlSession sqlSession = factory.openSession();
這一篇我們主要講解的是,mybatis是怎麼擷取Mapper的,擷取的Mapper對象又是什麼樣的?也就是下面的一句代碼
//通過Session建立UserDao接口代理對象
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
我們需要知道它的内部是怎麼實作的,那我們就繼續跟蹤源碼,一步一步走下去
在
DefaultSqlSession
類中
@Override
public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type, this);
}
它調用了configuration裡面的
getMapper()
方法,繼續跟蹤。
在
Configuration
類中
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
我們可以看見一個屬性叫做
mapperRegistry
,這是個什麼東西呢?從它的英文意思我們可以知道他叫mapper注冊器,我們看看它到底是什麼,我們看看
MapperRegistry
類
我們發現它其實就是維護了一個
HashMap
,這個map的key是你要建立的Mapper的class也就是例如
UserMapper.class
這樣的,value對應的是一個Mapper的代理工廠,工廠是什麼??工廠就是用來生産對象的啊,也就是說這個工廠是用來生産
Mapper
的代理對象的,我們回到我們的
Configuration
類裡面的
getMapper()
方法
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
我們再跟着進去,在
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);
}
}
首先我們看這句代碼
它是根據我們傳入的
UserMapper.class
作為key,從
knownMappers
擷取了一個
MapperProxyFactory
(Mapper代理工廠),為什麼這裡能擷取到?因為我們上一篇說了,它在建立
SqlSession
的時候就做了很多準備工作,在上一篇的時候我們一帶而過了其實我們現在可以看看
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
//以包的形式給出
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
//使用相對于類路徑的資源引用
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
//使用完全限定資源定位符(URL)
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
//使用映射器接口實作類的完全限定類名
} else if (resource == null && url == null && mapperClass != null) {
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
這段代碼是我們上一篇中提到的,它其實是在解析mybatis配置檔案,我們看這行代碼
它其實就是把我們的
UserMapper.class
加入到了
knownMappers
裡面了,現在我們知道了可以擷取到
MapperProxyFactory
,接下來就要生成代理對象了,我們回到
getMapper()
方法
@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);
}
}
現在我們拿到工廠類了,我們就可以開始生産代理對象了也就是下面的這句代碼
我們進去
newInstance()
方法看看,在
MapperProxyFactory
類中
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
它調用了本類中的另一個
newInstance()
方法
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
這個方法就再明顯不過了,就是一個動态代理,它的代理類是MapperProxy,我們再看看這個類
它實作了
InvocationHandler
接口,當執行方法的時候,執行的是
invoke()
方法,也就是下面的方法
@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 if (method.isDefault()) {
if (privateLookupInMethod == null) {
return invokeDefaultMethodJava8(proxy, method, args);
} else {
return invokeDefaultMethodJava9(proxy, method, args);
}
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
擷取Mapper的流程就結束了,關于具體方法的執行我們下一篇再說