一直在使用mybatis,对它的源码断断续续地看过一点,知之甚少,所以打算把mybatis源码坚持看一遍,并记录成一系列学习笔记,以免后面忘了,可以看着再回忆回忆。
先从最常用的点入手,在使用的时候,通过都是定义一个mapper接口,例如这样:
1 | public interface FoodMapper { |
然后在启动类上加上mapper扫描路径:
1 |
|
最后再在用到的地方注入使用:
1 |
|
这样一波操作看起来很简单,但都是mybatis和spring帮我做了很多事,所以先从这里入手,通过学习源码了解下是怎么样做到将mapper接口注册成bean,并再注入使用的。
首先,可以明确的一点是,是我通过使用@MapperScan
注解告诉了spring,mapper接口的所在位置,所以先看该注解的源码(以下源码版本为jdk1.8,mybatis-spring-boot-starter 2.0.1):
1 | (RetentionPolicy.RUNTIME) |
这个注解通过Import引入了MapperScannerRegistrar
,顾名思义,我先猜测它就是mapper的注册器,同时看到注解的注释有这样一句话:It performs when same work as {@link MapperScannerConfigurer} via {@link MapperScannerRegistrar}.
,意思就是MapperScannerRegistrar
和MapperScannerConfigurer
做的是同样的工作,而MapperScannerConfigurer
是干嘛的呢,使用xml配置时,通常这样指定mapper包路径,将mapper注册成bean:
1 | <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> |
所以点开MapperScannerRegistrar
,看它如何实现的:
1 | public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware { |
通过import引入MapperScannerRegistrar
之后,执行完aware接口的操作之后,将会调用上述核心的registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry)
,可以看到,在该方法中,先获取@MapperScan
注解的属性,不为空,将会调用私有的registerBeanDefinitions()方法,如下:
1 | void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry) { |
这里定义了ClassPathMapperScanner
对象scanner,然后获取注解属性,并将属性设置到scanner中,最后调用doScan开始扫描指定的包路径:
1 |
|
先调用父类也就是ClassPathBeanDefinitionScanner
的doScan方法,将指定包下的mapper接口转换成一个个bean定义持有者(BeanDefinitionHolder,拥有名称别名和bean定义,用于后续bean注册),并调用BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, registry)
注册bean定义,再将beanDefinitionHolder返回。如果不为空,将调用processBeanDefinitions对这些beanDefinitionHolder处理:
1 | private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) { |
这里对beanDefinition进行一系列改造,包括将beanClass设置为MapperFactoryBean
,此时,mapper接口是bean的原始类,但bean的实际类是MapperFactoryBean,这里将在后面注入mapper时体现。
这样,所有的mapper就注册进了Spring容器,上面说的是通过@MapperScan
注解指定mapper的包路径的方式,通常我们也会使用另一种方式,即不在启动类使用@MapperScan
注解,而是在每一个mapper接口上使用@Mapper
注解来声明这是一个mapper,然而对于这种方式,是怎样注册成bean的呢。
可以在MybatisAutoConfiguration
类中发现一个内部静态类AutoConfiguredMapperScannerRegistrar
,这个类在另一个内部静态类,同时也声明为配置类的MapperScannerRegistrarNotFoundConfiguration
引入:
1 | .springframework.context.annotation.Configuration |
而AutoConfiguredMapperScannerRegistrar
所做的和前文的MapperScannerRegistrar
做的是类似的事:
1 | public static class AutoConfiguredMapperScannerRegistrar |
即对根目录路径下所有类进行扫描,将有@Mapper
注解修饰的接口注册成bean。
以上就是mapper接口注册到Spring容器的分析,下面再探索下当我们使用@Autowired尝试注入时,都做了什么事。
上文已经说了,在扫描注册beanDefinition之后,有一步将bean的class改成了MapperFactoryBean,而MapperFactoryBean
实现了FactoryBean
接口,实现此接口的bean不能用作普通的bean,它是对象的工厂,不能直接作为bean的实例,Spring在处理这类bean时,会调用getObject()
获取bean对象。
同时MapperFactoryBean
继承自SqlSessionDaoSupport
,而SqlSessionDaoSupport
继承DaoSupport
,
DaoSupport
定义了初始化的方法,SqlSessionDaoSupport
定义了设置数据访问的方式,如setSqlSessionTemplate、setSqlSessionFactory(这两个方法本质上都是为了给属性SqlSessionTemplate sqlSessionTemplate
赋值),所以MapperFactoryBean
在创建对象时需要设置这两个值其中一个,如果都设置了,setSqlSessionFactory中逻辑将会被覆盖。
DaoSupport
实现了InitializingBean
接口,所以在Spring容器通过反射创建MapperFactoryBean
实例后,将会调用afterPropertiesSet()
:
1 |
|
随即调用本类的checkDaoConfig()
:
1 |
|
通过调用Configuration
的addMapper调用MapperRegistry.addMapper()
,将当前bean的原始类(该mapper接口)包装成MapperProxyFactory
对象保存至map缓存起来,如下所示:
1 | public <T> void addMapper(Class<T> type) { |
上面还有一个地方比较重要:MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);parser.parse();
我再看下parse()这个方法:
1 | public void parse() { |
这里主要是遍历mapper中的方法,将方法解析生成对应的MapperStatement
对象,并保存在Configuration
的Map<String, MappedStatement> mappedStatements
中,具体实现见MapperBuilderAssistant#addMappedStatement()
暂且不表。
衔接上文说的,MapperFactoryBean
实例创建之后,在注入时,通过调用getObject()
方法获取bean对象:
1 |
|
这里getSqlSession获取的是SqlSessionTemplate
,即SqlSessionTemplate#getMapper()
:
1 |
|
其实就是把上面说的放入MapperRegistry的map缓存中的MapperProxyFactory
对象取出来,再创建一个当前mapper接口的代理类对象(MapperProxy
),最后Spring将这个代理类对象注入到引用的地方:
1 | // MapperRegistry.java |
由于实际注入的是MapperProxy
对象,所以当我foodMapper.getById(1)
这样调用时,实际上调用的是MapperProxy#invoke()
:
1 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { |
将mapper的方法封装成MapperMethod
对象,调用MapperMethod
的execute方法,而在execute本质上是根据操作类型(insert、update、delete、select等)调用SqlSessionTemplate
的对应方法,而SqlSessionTemplate
通过内部的代理会话sqlSessionProxy
进行数据库操作,获取结果。
到这里,就看完了mapper注册与注入的流程。
关于mybatis与Spring整合,mapper的注册与注入,就学习到这里,还有一些瑕疵,后续再写笔记进行补充。