使用Mybatis时,最常看见的语句之一:
XXXMapper mapper = sqlSession.getMapper(XXXMapper.class);
复制代码
通过getMapper()
方法得到了一个XXXMapper
的对象。
要知道XXXMapper
本身是一个接口,而我们代码是没有写实现类的。所以该对象是由Mybatis通过:反射和动态代理,这两项技术创建了一个代理对象,现在我们来分析一下这个工程。
SqlSession
是一个接口,有一个实现类DefaultSqlSession
,实现了getMapper
方法:
@Override
public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type, this);
}
复制代码
我们会发现它调用了configuration.getMapper
方法,回想一下这个configuration
怎么来的。
回顾:
build
方式创建SqlSessionFactory
时,SqlSessionFactory
也是一个接口,它的默认实现类是DefaultSqlSessionFactory
。
sqlSessionFactory = new SqlSessionFactoryBuilder().build(xml);
复制代码
configuration
通过parser.parse()
返回
return build(parser.parse());
复制代码
并且调用build(Configuration config)
方法,创建了SqlSessionFactory
,拿到它后,我们要调用其openSession()
方法。
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
复制代码
拿到SqlSession
sqlSessionFactory
通过openSession()
方法,获得SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
复制代码
我们知道sqlSessionFactory
实质上是DefaultSqlSessionFactory
,所以其底层是调用DefaultSqlSessionFactory
的方法,而配置configuration
已经作为参数被传入其中了:
@Override
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
复制代码
我们会发现DefaultSqlSessionFactory
又调用了openSessionFromDataSource
,并且将configuration.getDefaultExecutorType()
作为参数传入。
先看openSessionFromDataSource
定义:
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
复制代码
我们注意到它是一个ExecutorType
类,我们去看
configuration.getDefaultExecutorType()
复制代码
会发现它就是配置了mybatis的默认执行器:SIMPLE
。 最后通过调用构造函数返回SqlSession,该构造器就是我们开头看见的代码:
public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
this.configuration = configuration;
this.executor = executor;
this.dirty = false;
this.autoCommit = autoCommit;
}
复制代码
SqlSession
是一个接口,有一个实现类DefaultSqlSession
,实现了getMapper
方法。SqlSessionFactory
是一个接口,它的默认实现类是DefaultSqlSessionFactory
,它调用openSession
方法,执行openSessionFromDataSource
,去生成DefaultSqlSession
。:
@Override
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
复制代码
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
...
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
...
} finally {
...
}
}
复制代码
拿到代理
回过头来,它是调用configuration.getMapper(type, this);
生成对象的。
@Override
public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type, this);
}
复制代码
该方法如下,将DefaultSqlSession
类作为参数传递,又调用了mapperRegistry.getMapper(type, sqlSession)
:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
复制代码
这里先不展开mapperRegistry
,它是一个映射器的注册器。我们就看调用的这个方法:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
#1 final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
#2 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);
}
}
复制代码
#1
和 #2
,一看抛出的异常,我们就理解了,我们的映射器如果没有注册在配置文件中的话,就会抛出"Type " + type + " is not known to the MapperRegistry."
异常。所以这里我们又可以倒回去去仔细研究配置文件中哪里去配置了这个knownMappers
。
(待补充)
这里贴出核心代码,在XMLConfigBuilder
中有这样一行代码,其中mapperClass
是字符串,我们通常会注册时写,写入的是mapper接口的全类路径名:
<mapper class="top.chengyunlai.mapper.UserAnnotationMapper"/>
复制代码
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
复制代码
如果注册在配置文件中的话,我们就要执行mapperProxyFactory.newInstance(sqlSession)
protected T newInstance(MapperProxy<T> mapperProxy) {
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);
}
复制代码
这里就明了,它是使用动态代理的方式。