mybatis中所有Dao接口的實(shí)現(xiàn)類(lèi)都是MapperProxy
問(wèn)題
問(wèn)題描述:在使用mybatis時(shí),我們只定義了接口,然后在XxxMapper.xml中寫(xiě)sql,若使用Mapper接口,甚至連XxxMapper.xml都可以省略,這一切究竟是如何實(shí)現(xiàn)的呢?
大多數(shù)項(xiàng)目都會(huì)使用spring+mybatis來(lái)搭建,本文參照mybatis-spring(1.2.0)分析上述問(wèn)題。
將mybatis集成到spring中,添加配置spring-dao.xml
<!-- 數(shù)據(jù)庫(kù)連接池信息 BoneCP configuration -->
<bean id="dataSource" class="com.jolbox.bonecp.BoneCPDataSource" destroy-method="close">
<property name="driverClass" value="${jdbc.driverClass}" />
<property name="jdbcUrl" value="${jdbc.jdbcUrl}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>
<!-- 集成Mybatis -->
<bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
</bean>
<!--MyBatis的DAO 無(wú)需實(shí)現(xiàn)類(lèi)配置 自動(dòng)掃描實(shí)現(xiàn)mapper.xml文件方法-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.foo.dao" />
<property name="sqlSessionFactoryBeanName" value="sessionFactory" />
</bean>
上述配置文件為mybatis的入口,閱讀源碼首先要找到入口,不能漫無(wú)目的地在源碼里閑逛。這里需要Spring的一點(diǎn)基礎(chǔ)知識(shí)(例如FactoryBean的作用)。與Spring的集成,本質(zhì)是是把自己扮成Spring的bean,然后Spring能感知到你的存在,從而對(duì)你進(jìn)行管理。咱們可以看到,mybatis在這里所有的配置信息,都是在<bean>標(biāo)簽里的。
這三個(gè)bean中,咱們挨個(gè)分析。
- id為dataSource的bean,最簡(jiǎn)單。只是封裝了一些數(shù)據(jù)庫(kù)的連接(包括配置)信息在里面。
- id為sessionFactory的bean,定義了SqlSessionFactoryBean,將dataSource bean作當(dāng)做了一個(gè)參數(shù)。暫且不管,繼續(xù)。
- 最后一個(gè)bean,可以看到,沒(méi)有定義id,說(shuō)明沒(méi)有人顯示地引用它。其實(shí)它就是spring-mybatis的入口。參數(shù)basePackage 是我們的Dao接口包,用于后面接口掃描使用。后一個(gè)參數(shù)sqlSessionFactoryBeanName,只是簡(jiǎn)單地給他賦了值。
咱們現(xiàn)在已經(jīng)知道spring-mybatis的入口了,開(kāi)干吧。

以下的分析內(nèi)容,可以認(rèn)為是在對(duì)image1的解釋。
進(jìn)入MapperScannerConfigurer,
BeanDefinitionRegistryPostProcessor,
InitializingBean,
ApplicationContextAware,
BeanNameAware
發(fā)現(xiàn)其實(shí)現(xiàn)了這四個(gè)接口。又是Spring的知識(shí)了,這些都是spring的擴(kuò)展點(diǎn),不多做介紹。重點(diǎn)看,BeanDefinitionRegistryPostProcessor。這個(gè)接口里只有一個(gè)方法postProcessBeanDefinitionRegistry,會(huì)在BeanDefinition加載完之后執(zhí)行。進(jìn)入該方法:
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
//ignore unrelated code
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
忽略非重點(diǎn)代碼。省略的代碼,是在設(shè)置屬性。重點(diǎn)看保留下來(lái)的最后一句代碼。這句話(huà)的意思是掃描包。不知各位還記得spring-dao.xml中這么一句話(huà):
<property name="basePackage" value="com.foo.dao" />
那么這里的scan(),掃描了com.foo.dao包下面的所有類(lèi),即咱們定義的DAO接口。根據(jù)scan()方法層層推進(jìn),來(lái)到了,org.mybatis.spring.mapper.ClassPathMapperScanner#doScan()。這個(gè)方法里有三句話(huà)比較重要:
① definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());
② definition.setBeanClass(MapperFactoryBean.class);
③ definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
①:將我們自定義的DAO接口作為參數(shù)放入BeanDefinition中,屬性名為mapperInterface;
②:設(shè)置BeanDefinition的BeanClass,MapperFactoryBean。這里對(duì)于文章開(kāi)頭“mybatis中所有Dao接口的實(shí)現(xiàn)類(lèi)都是MapperProxy”的這句話(huà),已初見(jiàn)端倪;
③:新增屬性名為sqlSessionFactory的屬性。
從這里可以看出兩點(diǎn):
- 咱們自定義的DAO接口,被新增了兩個(gè)屬性(mapperInterface與sqlSessionFactory),那么在創(chuàng)建bean的時(shí)候,會(huì)給這兩個(gè)屬性賦值,即調(diào)用這兩個(gè)屬性的set方法;
- DAO接口BeanDefinition的BeanClass是MapperFactoryBean。并且細(xì)心的小伙伴發(fā)現(xiàn),這是一個(gè)工程bean(FactoryBean),在創(chuàng)建bean時(shí),是從其getObject()方法獲取真正的bean。
BeanDefinition加載完后,開(kāi)始創(chuàng)建bean。
image2.png
開(kāi)始image2的分析:
由于我們自定義DAO接口的BeanClasss是MapperFactoryBean,所以創(chuàng)建的是MapperFactoryBean,遂進(jìn)入MapperFactoryBean源碼。
上文咱們提到,會(huì)調(diào)用sqlSessionFactory的set方法(setSqlSessionFactory)。發(fā)現(xiàn)該方法在MapperFactoryBean的父類(lèi)SqlSessionDaoSupport中,進(jìn)入:
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
if (!this.externalSqlSession) {
this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
}
}
層層跟進(jìn),直至最底層構(gòu)造方法:
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
notNull(executorType, "Property 'executorType' is required");
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
this.sqlSessionProxy = (SqlSession) newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class },
new SqlSessionInterceptor());
}
此處咱們看到了jdk動(dòng)態(tài)代理(SqlSessionInterceptor),這也是咱們image2中的標(biāo)注的第一次動(dòng)態(tài)代理。跟進(jìn):
private class SqlSessionInterceptor implements InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
final SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
try {
Object result = method.invoke(sqlSession, args);
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
// force commit even on non-dirty sessions because some databases require
// a commit/rollback before calling close()
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
Throwable unwrapped = unwrapThrowable(t);
if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
if (translated != null) {
unwrapped = translated;
}
}
throw unwrapped;
} finally {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
與數(shù)據(jù)庫(kù)的操作都是通過(guò)SqlSession來(lái)完成的。這里咱們看到生成了sqlSession,然后根據(jù)具體情況,來(lái)判斷是否自動(dòng)提交。
setSqlSessionFactory()方法到這算基本講完了,新建了一個(gè)SqlSessionTemplate,回到MapperFactoryBean。
由于MapperFactoryBean是FactoryBean的實(shí)現(xiàn)類(lèi),所以會(huì)調(diào)用其getObject()方法來(lái)獲取真正的bean。進(jìn)入該方法:
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
無(wú)實(shí)際內(nèi)容,重點(diǎn)內(nèi)容都在getMapper()里面。by the way,這里的mapperInterface,咱們?cè)趇mage1里有提到,若遺忘,往上翻。由于咱們剛才通過(guò)setSqlSessionFactory()方法,新建了一個(gè)SqlSessionTemplate,所以這里通過(guò)SqlSessionTemplate#getMapper()層層跟進(jìn),直至出現(xiàn)明顯有特殊意義的代碼,到了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);
}
}
跟進(jìn)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<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
這兩個(gè)方法,其實(shí)又是一次動(dòng)態(tài)代理(image2的第二次動(dòng)態(tài)代理)。
回到getMapper(),最后返回了MapperProxy 對(duì)象。

通過(guò)image2的分析,咱們知道,我們自定義的DAO接口,最后被封裝成了MapperProxy。當(dāng)我們?cè)谡{(diào)用DAO接口里面的方法時(shí),實(shí)際是調(diào)用了MapperProxy#invoke()。
進(jìn)入MapperProxy的invoke()方法:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
這里通過(guò)動(dòng)態(tài)代理獲取到了MapperMethod方法,然后開(kāi)始execute。進(jìn)入:
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
if (SqlCommandType.INSERT == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
} else if (SqlCommandType.UPDATE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
} else if (SqlCommandType.DELETE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
} else if (SqlCommandType.SELECT == command.getType()) {
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
} else {
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
這一大段代碼基本都是比較熟悉的代碼。根據(jù)SqlCommandType判斷是增刪改查的哪一個(gè),然后調(diào)用SqlSession對(duì)應(yīng)的方法去執(zhí)行。請(qǐng)記住,這里的SqlSession已經(jīng)被動(dòng)態(tài)代理過(guò)了,實(shí)際上是調(diào)用了SqlSessionInterceptor#invoke()方法。
As always and let me know what you think, leave the comments below. Bye :)
