参考
深入剖析 mybatis 原理(三)如何整合Spring
没有他的参考,我不可能做出这篇理解。
@mapperScan注册MapperFactoryBean
注册的那些MapperFactoryBean(是一种FactoryBean)会在spring初始化时调用其getObject
方法生成具体Bean。(关于FactoryBean,可参考Spring源码学习--FactoryBean实现原理)
MapperFactoryBean初始化
但在MapperFactoryBean实例化后,调用getObject
之前,由于MapperFactoryBean本身也是个Bean,spring框架会调用setSqlSessionFactory和setSqlSessionTemplate设置其属性。以便之后的getObject
使用。
// SqlSessionDaoSupport.java
// MapperFactoryBean继承了SqlSessionDaoSupport
// 设置sqlSessionFactory, 顺便自动设置sqlSessionTemplate。所以如果没设定sqlSessionTemplate也没关系。
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
if (this.sqlSessionTemplate == null || sqlSessionFactory != this.sqlSessionTemplate.getSqlSessionFactory()) {
this.sqlSessionTemplate = createSqlSessionTemplate(sqlSessionFactory);
}
}
// 可以手动设置
public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
this.sqlSessionTemplate = sqlSessionTemplate;
}
在之后的getObject
中:
-
getSqlSession
就用到了刚才设置的sqlSessionTemplate。 -
getMapper(this.mapperInterface)
在深入剖析 mybatis 原理(二)已经分析过了
,该方法最终会返回Mapper代理类对象。
// MapperFactoryBean.java
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
// SqlSessionDaoSupport.java
public SqlSession getSqlSession() {
return this.sqlSessionTemplate;
}
getObject的调用
你可能会问,那这个MapperFactoryBean.getObject何时会调用呢?
还记得@mapperScan注册MapperFactoryBean时的ClassPathMapperScanner.processBeanDefinitions
方法吗:
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
String beanClassName = definition.getBeanClassName();
LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName()
+ "' and '" + beanClassName + "' mapperInterface");
// the mapper interface is the original class of the bean
// but, the actual class of the bean is MapperFactoryBean
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
definition.setBeanClass(this.mapperFactoryBeanClass);
// ...
}
}
}
用途有两个:
- 这里为Bean注册的名字beanClassName是
xx.xx.xxMapper
,但实际的Bean是MapperFactoryBean。
在springboot初始化的过程中,会对每个Bean调用getBean
进行实例化,因此MapperFactoryBean.getObject也会被调用。 - Service初始化时,spring会处理@Autowire中的Mapper,根据其类型的名字
xx.xx.xxMapper
找Bean,然后调用beanFactory.getBean(beanName);
。但根据这个名字找到的其实是刚才注册的MapperFactoryBean。由于它是FactoryBean,对其调用getBean
只会触发getObject
,从而返回代理类对象。
要验证@Autowired在Mapper上的作用,请自行调试,可参考后文。
调试@autowired的Mapper的注入
- 在
AutowiredAnnotationBeanPostProcessor.inject
中的Object arg = beanFactory.resolveDependency(currDesc, beanName, autowiredBeans, typeConverter);
打断点调试。 - 当变量
beanName
名字是包含了Mapper的Service或Controller时,进入该方法。
然后跟随进入result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
->instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this);
->beanFactory.getBean(beanName);
。进入调试。熟悉BeanFactory.getBean的人应该不用调试就知道,这个名字对应的Bean是MapperBeanFactory,也就是一个BeanFactory,所以最终该方法beanFactory.getBean(beanName);
返回的是MapperFactoryBean的getObject的返回值。这个getObject就是刚才分析的用途啦!
总结
- @MapperScan扫描指定的包,对每个Mapper,以它的名字注册了实际类型是MapperFactoryBean的Bean定义。
- 有了这些Bean定义,在spring实例化Bean时,这些MapperFactoryBean会被实例化、初始化,对应的方法也会设置
- 在处理@autowired标注的Mapper时,会返回MapperFactoryBean.getObject的调用结果,也就是
getSqlSession().getMapper(this.mapperInterface);
了。 - 上一步的结果会导致
@Autowired SomeMapper mapper;
上注入了一个Mapper代理类,该代理类会将所有数据库请求都移交给底层的SqlSession操作。 - 上一步中,Mapper移交到的sqlSession其实是个SqlSessionTemplate,SqlSessionTemplate又将一切数据库操作移交给sqlSessionProxy,而后者是基于SqlSessionInterceptor创建的代理类。
也就是说,SqlSessionTemplate的数据库操作会被SqlSessionInterceptor.invoke所拦截。 - SqlSessionInterceptor.invoke中调用
getSqlSession
方法,其内部在需要的时候调用session = sessionFactory.openSession(executorType);
获取新的session,其实也就在开启新的连接。
也就是说SqlSessionTemplate的数据库操作会被SqlSessionInterceptor.invoke所拦截,每次操作前都要获取到SqlSession(实际类型是DefaultSqlSession),这个SqlSession:- 要么是复用现有的(比如复用当前事务使用的)
-
要么是新建的。
所以上文的完整图解应当改为如下: