前言
我们使用Spring+Mybatis做开发时,一般都会引入spring-mybatis这个包,这个包是mybatis自己开发的,它利用Spring的扩展接口将自己很好的整合在Spring中,下面就来说一下,我们定义的mapper接口,是怎么把它变成一个实际的对象然后存储在容器中的
前期准备
我们先来准备下环境
配置Spring环境
引入Spring包,引入mybatis包,引入Spring跟Mybatis整合的包,引入数据库连接驱动的包
AppConfig.java,该类只是简单的配置了Bean的扫描包
1 | package com.demo.config; |
MybatisConfig.java,该类配置了数据库连接池和SQLSessionFactory,以及Mapper接口的扫描
1 | package com.demo.config; |
UserDao.java,写一个简单的接口,用于查询数据库
1 | package com.demo.dao; |
Test.java最后写一个测试启动类
Test.java
1 | package com.demo.test; |
这样一来项目即可运行并输出
问题
通过上面的小demo,我们可以通过UserDao的selectOne方法查询数据库,但是我有一个问题,为什么一个UserDao可以实例化?
我们输出从容器中得到的UserDao的类看下
1 | UserDao dao = annotationConfigApplicationContext.getBean(UserDao.class); |
输出结果为
1 | org.mybatis.spring.mapper.MapperFactoryBean@6913c1fb |
看来,一个Mapper接口变成了一个MapperFactoryBean了
这里提出几个问题?
1、MapperFactoryBean是什么东西?
2、它是怎么进入容器,然后被Spring锁管理的?
MapperFactoryBean是什么东西
我们看下MapperFactoryBean的源码,发现它实现了Spring的FactoryBean
FactoryBean
FactoryBean是Spring中特殊的Bean,它可以通过getObject方法生成指定的Bean属性。
当我们手动向Spring容器中注册BeanDefinition(简称BD) 时,可以设置BD为FactoryBean类型,这样在Spring中获取这个Bean时,就会使用FactoryBean的getObject创建Bean
如何手动向Spring容器中注入BeanDefinition
何为BeanDefinition?
BeanDefinition是Bean的描述,如果把Bean比作一道菜的话,那么BeanDefinition就是做这道菜的原料。在Spring中,Bean的创建是通过BeanDefinition来创建的,所有属性都包含在BeanDefinition中,比如单例模式,懒加载,类名,方法….
如何手动的向Spring中注册BD?
在Spring中,只要配置类实现了ImportBeanDefinitionRegistrar接口,就可直接向Spring中注册BD
org.springframework.context.annotation.ImportBeanDefinitionRegistrar
比如
1 | package com.lnw.config; |
这样的话就可以自己手动的在Spring容器中添加一个Pear(梨)的Bean了
继续刚才FactoryBean的话题
如果我有两个类,Apple和Pear
我手动先容器中注册了Pear,我可以通过FactoryBean来改变Pear从而获取Apple
先编写一个类实现FactoryBean接口
1 | package com.lnw.test; |
重点就是getObject方法,它返回一个Apple对象
然后,我们手动注册一个Pear的BD,把它的类型改为我们规定的FactoryBean类
1 | package com.lnw.config; |
然后我们从容器中获取
1 | System.out.println(ac.getBean("pear").getClass()); |
结果输出
1 | class com.lnw.dao.Apple |
这就是FactoryBean的功能和作用
@MapperScan注解
通过上面我们可以大概知道,spring-mybatis就是通过设置MapperFactoryBean解决从一个Mapper接口变成一个可实际操作的对象的过程
那MapperFactoryBean在哪里设置进去的呢?
答案就在我们的@MapperScan注解中
我们打开注解
@MapperScan
发现它引入了一个类:MapperScannerRegistrar
MapperScannerRegistrar.java
1 | public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware { |
它实现了ImportBeanDefinitionRegistrar,代表它手动的向Spring中注册了BD
它里面还有一个扫描器,通过扫描器把mapper接口扫描出来,并设置为MapperFactoryBean类型我们直接看源码
org.mybatis.spring.mapper.ClassPathMapperScanner#doScan
1 | public Set<BeanDefinitionHolder> doScan(String... basePackages) { |
org.mybatis.spring.mapper.ClassPathMapperScanner#processBeanDefinitions
1 | private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) { |
代码很长,但是主要的就是中间那两句代码!第一句是设置产生的Bean的类型,通过构造方法传入;第二句是设置BD的类型为FactoryBean,这样才能在创建Bean的时候调用getObject方法
这样一来,Spring-Mybatis在getObject方法中,通过动态代理为Mapper接口创建代理对象,产生一个可操作的Bean,创建代理对象的过程以后再讨论。
总结
我们的@MapperScan注解,会引入MapperScannerRegistrar类,这个类实现了ImportBeanDefinitionRegistrar的底层Spring接口,这个接口可以手动的向Spring注册BD,Spring使用这个BD创建Bean。
但是在注册BD之前mybatis把BD的属性设置为MapperFactoryBean,MapperFactoryBean实现了Spring底层的FactoryBean,FactoryBean可以指定生成什么样的Bean。所以MapperFactoryBean的getObject方法中,通过传入接口的属性,使用动态代理技术创建一个实现了Mapper接口的代理对象