一直在使用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的注册与注入,就学习到这里,还有一些瑕疵,后续再写笔记进行补充。