mybatis源码初探(二) 整合spring详细原理+源码

mybatis和spring的整合
1.mybatis工作流程回顾

首先回顾一下mybatis的启动过程(详情可以参考上一篇文章):

String resource = "resources/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
// 读取配置文件获取sqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 获取session
SqlSession session = sqlSessionFactory.openSession();
// 获取mapper代理类
FsiptUserMapper mapper = session.getMapper(FsiptUserMapper.class);
// 查询(执行代理类invoke方法)
FsiptUser fsiptUser = mapper.selectUser("S67190");
System.out.println(fsiptUser.toString());

大致流程还是比较清晰的:
在这里插入图片描述
获取到代理对象mapper之后,就可以利用mapper进行crud等操作了。所以mybatis整合spring的过程,一句话来说就是:把代理对象mapper作为一个bean放入spring容器,然后像使用普通的bean一样使用这个代理对象

2.简单回顾spring中bean的实例化过程
“标准化”过程

   简单回顾下spring中bean的实例化的“标准化”过程:
在这里插入图片描述

“定制化”过程

   除了“标准化”过程之外,spring还提供了一种生成bean的方式,利用spring中的FactoryBean。简单来说,Spring提供了一个org.springframework.beans.factory.FactoryBean的工厂类接口,我们可以通过实现该接口定制实例化Bean的逻辑。如果一个Bean实现了FactoryBean接口的Bean,那么和普通Bean不同的是,实例化的时候从BeanFactory中获取的实际上是FactoryBean的getObject()方法返回的对象,而非FactoryBean对象。
在这里插入图片描述

3.整合原理概述(两个标签完成整合)

(这篇文章写的很好)
   回到主题,我们的目标是在spring中生成一个bean(代理对象mapper),如果按照正常spring生成bean的流程,生成一个mapper对象就3步:

  • 生成一个BeanDefinition(可以理解成BeanDefinitoin bd = new BeanDefinitoin());
  • 加载类(可以理解成bd.setBeanClassName(MapperProxy.class.getName()));
  • 放入spring容器(可以理解成SpringContainer.addBd(bd)

   如果我们可以通过某种方式获取这个bean对象(mapper)的BeanDefinition(后面统一简写为bd),将bean对象的类信息赋值给bd,然后把bd交给spring。那么spring就可以根据这个bd自动生成bean对象。
   听起来似乎很简单,不过有个很重要的问题:mapper是个jdk动态代理生成的代理对象,我们只有它对应的接口没有它对应的类,并不清楚它对应的类信息。所以这种“标准化”流程行不通。实际上mybatis走的是另一条路,即上文的“定制化”流程。下面详细介绍。
   完成整合工作的是mybatis-spring这个jar包:
在这里插入图片描述
spring项目中整合mybatis相关的xml:
在这里插入图片描述
以及
在这里插入图片描述
其实整合只需要配置这两个bean:SqlSessionFactoryBean和MapperScannerConfigurer,可见这两个bean的作用至关重要。本文主要介绍利用xml文件进行整合的过程,也可以利用注解(@MapperScan、@MapperScannerRegistrar),二者仅有形式上的区别。
这里建议首先理解mybatis的启动过程,这对于理解mybatis和spring的整合事半功倍。

4.SqlSessionFactoryBean标签的作用

   很粗暴,直接点进SqlSessionFactoryBean看看,发现它实现了三个接口:
在这里插入图片描述
其中ApplicationListener接口和整合没关系,只看InitializingBean和FactoryBean接口,这俩接口规定SqlSessionFactoryBean要实现afterPropertiesSet()和getObject()方法。

  • afterPropertiesSet()方法
    闻香识女…不好意思,是看名字猜这个方法的功能,此方法应该是在“设置完属性后”执行的。事实上正是如此,spring在创建bean的过程中首先调用的就是这个方法:
    在这里插入图片描述
    执行了buildSqlSessionFactory()方法:在这里插入图片描述
    如果了解过mybatis的启动过程,就会对这个方法里的很多似曾相识。XMLConfigBuilder:用于解析mybatis配置;XMLMapperBuilder:用于解析sql源;configuration:各种配置解析完之后封装成的对象。最后的build方法:this.sqlSessionFactoryBuilder.build(configuration)更眼熟,都是得到了一个SqlSessionFactory,点进去发现生成的是一个DefaultSqlSessionFactory,和mybatis的流程一样样的:
    在这里插入图片描述
    这里可以对比下mybatis获取SqlSessionFactory的相关代码:
    在这里插入图片描述
    当然了,在mybatis中这个SqlSessionFactory接下来还要获取一个SqlSession,这个SqlSession再执行getMapper()方法才获取到mapper代理对象的。这部分内容在整合的过程中怎么实现的,我们下面再讲。
  • getObject()方法
    在上面概述原理的时候我们说过,实现了FactoryBean接口的bean在获取实例的时候不是直接new的,而是调用其getObject()方法获取的。这个getObject()方法干了点啥呢,很简单,返回刚刚生成的SqlSessionFactory:
    在这里插入图片描述
    到这里第一个xml标签的作用已经很清楚了,就一件事,生成一个SqlSessionFactoryBean(或者说SqlSessionFactory的一个实例)。
5.MapperScannerConfigurer标签的作用

   MapperScannerConfigurer继承了org.mybatis.spring.mapper.MapperScannerConfigurer,同时覆写了postProcessBeanDefinitionRegistry()方法:
在这里插入图片描述
所以,要理解MapperScannerConfigurer这个bean干了些什么,重点就要看这个postProcessBeanDefinitionRegistry方法。进入这个方法,看到它首先调用了父类的同名方法:super.postProcessBeanDefinitionRegistry(registry);进入父类的方法,看到了定义扫描器,以及进行扫描的过程:
在这里插入图片描述
spring中在bean的实例化过程中就有扫描指定package下所有类得到BeanDefinition这一步,不难猜测,这里应该是在扫描mybatis中需要用的接口,究竟如何继续debug。

  • ClassPathMapperScanner扫描器
    看一下这个ClassPathMapperScanner的结构:
    在这里插入图片描述
    它的父类ClassPathBeanDefinitionScanner是spring的扫描器,说明它的扫描过程和spring的扫描过程是类似的。我们进入ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry)
    在这里插入图片描述
    这里有一个点,在spiring默认配置中useDefaultFilter是true,可理解为启用默认扫描器。默认扫描器时会扫描@Component,@Repository,@Service,@Controller四种注解:
    在这里插入图片描述
    而mybatis的扫描器不启用默认扫描器。

  • 扫描并获取BeanDefinition
    生成扫描器之后就开始扫描了:
    在这里插入图片描述
    一路debug进入doScan()方法:
    在这里插入图片描述
    这里看到先调用了父类的doScan方法,说明和spring扫描在流程上是一样的。然后扫描得到BeanDefinition后再执行processBeanDefinitions(beanDefinitions)方法对BeanDefinition进行一些处理。现在来简单看一下扫描流程:
    在这里插入图片描述
    详细的扫描流程这里就不介绍了,上面红框中的findCandidateComponents(basePackage)方法看名字也知道,是在挑选符合条件的“候选者”,mybatis怎么挑选呢?进到这个方法中:
    在这里插入图片描述
    isCandidateComponent方法中可以看到,mybatis会把接口作为“候选者”:
    在这里插入图片描述
    因为我们关注的是整合过程,那到这里基本上就走完了扫描流程,mybatis会挑选出我们定义的各种Mapper接口,把这些接口的相关信息封装成BeanDefinition(从刚才的截图能看到具体的类是ScannedGenericBeanDefinition)。

  • 处理扫描得到的BeanDefinition
    处理BeanDefinition也是mybatis和spring整合的重头戏,也就是processBeanDefinitions(beanDefinitions)方法:
    在这里插入图片描述
    进入这个方法,我们要重点关注这里:
    在这里插入图片描述
    可以看到,这个bd的类型原本还是刚刚扫描得到的接口类,但是随后被改成了MapperFactoryBean类型。这个MapperFactoryBean前文提到过,认真的同学已经似曾相识,而不认真的同学一脸懵b。这里我们先放一放,接着往下看:
    在这里插入图片描述
    在改了bd的Class之后,进一步还会对bd进行一次属性的设置,这个属性的类型就是我们配置文件中配置的那个SqlSessionFactoryBean。而我们知道一个bean实例化后会进行依赖注入,因此这里的这个SqlSessionFactoryBean的实例(也就是上图中的sqlSessionFactoryFsiptDB)将来会作为依赖被注入进来。注意到这里的属性传的是new RuntimeBeanReference(this.sqlSessionFactoryBeanName),什么意思呢?这其实是对sqlSessionFactoryFsiptDB的一个动态引用,因为此时sqlSessionFactoryFsiptDB很可能还没准备好。而在真正进行依赖注入时,Spring会将该引用替换成实际生成的Bean对象。这个道理就和xml配置中的"ref"一样:

<bean class="tk.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="sqlSessionFactory" ref="sqlSessionFactoryFsiptDB" />
</bean>

  到这里第二个xml标签的作用也讲完了,做了两件事:扫描得到bd后,改beanClass和设置SqlSessionFactoryBean作为它的一个属性。
  至此我宣布,mybatis和spring的整合已经完成了!不过对比mybatis的启动过程,好像还没看到通过jdk动态代理获取mapper代理对象这个过程。如果你有这个疑问,请接着往下看。

6.mybatis如何获取mapper代理对象

  在所有的整合配置完成的情况下,获取mapper代理对象其实也很简单了。在这之前,首先简单回顾下mybatis获取mapper代理对象的样子:
在这里插入图片描述
所以,mapper代理对象是利用这个session的getMapper方法获取的,而session是从SqlSessionFactory中获取的。
  接着来看和spring整合后的mybatis怎么操作。一般我们在项目里都是这么用的:

@Service("FsiptDeployServiceImpl")
public class FsiptDeployServiceImpl implements IFsiptDeployService {
	@Autowired
    private InternetFsiptUserMapper internetFsiptUserMapper;

	public void crud(){
		...
		internetFsiptUserMapper.selectAll();
		...
	}
}

internetFsiptUserMapper在作为依赖注入进入FsiptDeployServiceImpl时会被实例化,在实例化的时候会根据InternetFsiptUserMapper的名字(也就是“internetFsiptUserMapper”,首字母小写)去获取其bd(这是spring的知识),然后发现它的bd中类信息是MapperFactoryBean类型,看一下这个MapperFactoryBean的类图:
在这里插入图片描述
发现它的是SqlSessionDaoSupport的一个子类,同时实现了FactoryBean接口,而我们前面提到过,实现了FactoryBean接口的bean在实例化的时候,是通过其getObject()方法获取实例的:
在这里插入图片描述
有点眼熟,这就是mybatis中通过sqlSession获取mapper代理对象的步骤。继续进入getSqlSession()发现来到了父类SqlSessionDaoSupport中,就是返回了一个sqlSession:
那么最后一个问题来了,这个sqlSession好像没看到什么时候设置的。其实这涉及到spring的知识点:
在这里插入图片描述
在spring中,SqlSessionDaoSupport作为MapperFactoryBean的父类,在MapperFactoryBean进行依赖注入的时候,除本身的属性之外,其父类SqlSessionDaoSupport的属性也会被注入进来。Spring中依赖注入的方式有多种,其中一种就是通过set方法注入依赖。在父类进行依赖注入时,通过set方法将SqlSessionFactory的实例注入进来的,而这里注入的这个SqlSessionFactory的实例是哪个呢?在扫描的过程中已经设置好了,正式之前提到过的:
在这里插入图片描述

至于sqlSessionFactoryFsiptDB的生成过程,就是我们讲的第一个xml标签:
在这里插入图片描述

7.关于使用纯注解的整合

  使用纯注解这里不详细说了,但是原理没有区别,只是具体操作方式有区别,无非是标签换成注解而已:
在这里插入图片描述
补充:在mybatis-spring 2.0.5版本后,可以不再使用@MapperScan,转而定义一个普通的bean:
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值