Spring整合Mybatis源码分析原理

目录

前言

正文

基于XML配置

基于注解

总结

课程推荐


前言

目前虽然SSM框架的整合使用的步骤已经被Spring boot的自动配置给代替了,但是我认为一个初学者或者是直接使用Spring boot脚手架框架的小伙伴们是一定要懂他们之间的整合原理,因为谈到框架整合,其实Spring boot也就是整合自动配置内容然后通过Spring的高扩展接口注入到IoC容器中,所以整合的思想他是不变化的。而且呢,博主认为当代程序员不缺技术,缺的是思想,假如那天Spring boot淘汰了,公司叫你来整合一些自动配置怎么办呢?所以今天给小伙伴们带来Spring整合Mybatis的原理分析原理。

正文

基于XML配置

入口在哪里?

我认为框架源码好比一棵树,树(各种集合的整合)上有很多枝干(将零散的知识整合起来的一个集合),枝干上面又很多叶子(零散的知识)。入口好比你要爬上树看枝干和枝干和叶子,而爬上树的位置就是入口。

那么Spring整合Mybatis的入口在哪里?

整合的时候我们不是写了几个XML配置文件吗?比如mybatis-config,spring-mybatis之类的,写XML的目的也就是将各种bean注入到IoC容器中。所以我们的入口就是在XML配置中。

  <!--3.sqlSessionFactory-->
  <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <!--引用上述数据源-->
    <property name="dataSource" ref="dataSource"/>
    <!--绑定MyBatis配置文件-->
    <property name="configLocation" value="classpath:mybatis-config.xml"/>
  </bean>
 
  <!--4.配置dao接口扫描包,动态实现了Dao接口可以注入到Spring容器中-->
  <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <!--注入sqlSessionFactory-->
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
    <!--要扫描的dao包-->
    <property name="basePackage" value="dao"/>
  </bean>

看似一个简单的XML注入bean的过程,其实内置了很多操作,我们看到SqlSessionFactoryBean。

就这么说,要弄懂整合原理,这三个接口起码要弄懂前两个接口,第三个也就是一个监听的扩展接口,FactoryBean博主也是写了文章,可以先去学习一下。

FactoryBean的使用和源码解析icon-default.png?t=M1FBhttps://blog.csdn.net/qq_43799161/article/details/122927440?spm=1001.2014.3001.5502

FactoryBean:Spring的一个扩展点,也是一个bean对象注入到IoC容器中,但是跟普通Bean的区别就是,FactoryBean也可以生产bean,并且在一定条件下实现FactoryBean接口的实现类就是FactoryBean生产的实例对象。

InitializingBean:实现此接口的bean对象,在赋值操作后的一个回调接口。

所以我们看到InitializingBean接口的实现方法。

buildSqlSessionFactory()方法太过于长所以不截图上来,方法一看就便能知道干了些啥,我们XML配置不是配置了configLocation属性吗?而configLocation属性配置的内容也就是引用mybatis自带的XML配置,学习过Mybatis的同学便可知道,没学过的建议看一下博主的Mybatis源码解析文章。这里就是在通过XPathParser和XNode解析XML然后添加到Configuration容器中,解析完以后通过SqlSessionFactoryBuilder工厂和参数Configuration容器来生成SqlSessionFactory对象。这里就不对Mybatis的操作过于多讲,基础比较差的同学可以先去学习一下Mybatis。

Mybatis源码解析icon-default.png?t=M1FBhttps://blog.csdn.net/qq_43799161/article/details/122753401?spm=1001.2014.3001.5502

目前我们已经解析完XML配置而且对SqlSessionFactory对象初始化了,那么我们FactoryBean接口又做了一些什么呢?

这里重写FactoryBean接口中的方法。一眼便知getObject()方法返回的是SqlSessionFactory对象,而在前面已经重写了InitializingBean接口的方法初始化SqlSessionFactory对象了。

我们要知道FactoryBean在特定条件通过getBean()方法返回的是getObject()方法返回的对象。

所以我们再看到最初的XML配置。

  <!--4.配置dao接口扫描包,动态实现了Dao接口可以注入到Spring容器中-->
  <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <!--注入sqlSessionFactory-->
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
    <!--要扫描的dao包-->
    <property name="basePackage" value="dao"/>
  </bean>

再继续往下追之前,我们先思考,在单独使用Mybatis的时候我们是使用SqlSessionFactory生产出SqlSession对象,然后再通过SqlSession对象getMapper(***.class)对接口做一个动态代理,然后再调用动态代理的方法与数据库交互。

而使用了Spring整合的Mybatis以后,就只需要通过@Autowired注入Dao层的接口就可以直接使用Dao层的接口。

而我们之前的操作也就是通过FactoryBean将SqlSessionFactory注入到容器中,但是也达不到直接@Autowired注入Dao层的接口就可以直接使用Dao层的接口。所以推理一下就能知道MapperScannerConfigurer的作用肯定实现这一功能,所以我们追进去。

看到这个接口我们就知道mybatis是通过这个spring高扩展接口将dao层接口注入进去,我们看到实现方法postProcessBeanDefinitionRegistry()

  @Override
  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    
    // 判断是否使用properties配置文件配置,如果使用了就将值解析获取
    if (this.processPropertyPlaceHolders) {
      processPropertyPlaceHolders();
    }

    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
    scanner.setAddToConfig(this.addToConfig);
    scanner.setAnnotationClass(this.annotationClass);
    scanner.setMarkerInterface(this.markerInterface);
    scanner.setSqlSessionFactory(this.sqlSessionFactory);
    scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
    scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
    scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
    scanner.setResourceLoader(this.applicationContext);
    scanner.setBeanNameGenerator(this.nameGenerator);
    scanner.registerFilters();

    // 扫描xml配置传入的basePackage包下的接口
    scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }

 我们先看到ClassPathMapperScanner()类的构造方法也将父类初始化了,核心也就是这两个类。

直接看到scan()方法

再进入到doScan()方法。

 这里比较绕,我们看到doScan()方法子类的实现。

这里有必要讲解一下,这里的子类使用super().doScan()调用父类的doScan()方法,所以先执行父类的doScan()方法,父类的doScan()方法是通过解析basePackages路径参数来获取到每个dao层接口BeanDefinition,然后再通过BeanDefinitionRegister(也就是DefaultListableBeanFactory)中的registerBeanDefinition()方法将BeanDefinition添加到上下文中,等待后面的初始化。注意这里只是父类的doScan()方法,并且父类的doScan()方法的返回值是BeanDefinitionHolder。而子类是Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);来接收。这里确实比较绕。

  private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    GenericBeanDefinition definition;
    for (BeanDefinitionHolder holder : beanDefinitions) {
      definition = (GenericBeanDefinition) holder.getBeanDefinition();

      if (logger.isDebugEnabled()) {
        logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName() 
          + "' and '" + definition.getBeanClassName() + "' mapperInterface");
      }

      // the mapper interface is the original class of the bean
      // but, the actual class of the bean is MapperFactoryBean
      definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
      definition.setBeanClass(this.mapperFactoryBean.getClass());

      definition.getPropertyValues().add("addToConfig", this.addToConfig);

      boolean explicitFactoryUsed = false;
      if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
        definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
        explicitFactoryUsed = true;
      } else if (this.sqlSessionFactory != null) {
        definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
        explicitFactoryUsed = true;
      }

      if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
        if (explicitFactoryUsed) {
          logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
        }
        definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
        explicitFactoryUsed = true;
      } else if (this.sqlSessionTemplate != null) {
        if (explicitFactoryUsed) {
          logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
        }
        definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
        explicitFactoryUsed = true;
      }

      if (!explicitFactoryUsed) {
        if (logger.isDebugEnabled()) {
          logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
        }
        definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
      }
    }
  }

 MapperScannerConfiguration中对ClassPathMapperScanner类中这些属性进行set赋值,而MapperScannerConfiguration的参数是我们通过XML进行配置的,所以这里也就是对我们是否配置了参数做一些判断,如果配置了就赋值到BeanDefinition中,而且也要注意这里的BeanDefinition也还是super.doScan()方法的返回值,也就是同一指向。确实很绕这里。

上面的代码中我们注意到有一行

definition.setBeanClass(this.mapperFactoryBean.getClass());

这里我还是来讲解一下吧,这个 MapperFactoryBean类也是继承了FactoryBean接口,而FactoryBean的getObject()方法返回的是当前mapperInterface(当前dao层的接口)的动态代理对象。而前面执行了definition.setBeanClass(this.mapperFactoryBean.getClass()),所以说当前的BeanDefinition的beanClass是mapperInterface(当前dao层的接口)的动态代理对象。也就是说最后是当前dao层的接口的动态代理对象注册到IoC容器,所以也就证明为什么可以直接通过@Autowirte将dao层的接口直接注入到Service层直接使用。

基于注解

以上是基于XML的形式,还有一种形式就是基于注解形式的@Mapper,相比大家对此很熟悉不过了,其实基于注解的跟XML的区别没啥,也就是一个是解析XML,一个是解析注解。

 看到这接口也是一样,就知道也是Spring的高扩展接口,也是将BeanDefinition添加到Spring的上下文中,我们看到实现方法registerBeanDefinitions();

这方法也就是获取到@Mapper的元注解信息然后解析,也是创建了ClassPathMapperScanner,最后也是走到了ClassPathMapperScanner类的doScan()方法,所以流程是一模一样的。

 

总结

后面的Mapper扫描确实比较绕,前面的SqlSessionFactoryBean的还是比较容易的,我觉得把确实就是很多树叶子成枝干,很多枝干变成树。零零散散的Spring高扩展点成就了Spring优势。大家对于Mapper扫描的部分自己多下功夫自己去追几遍,就算是没追成功,再来看帖子的讲解就能明白,而且有不明白的点可以来找博主,免费切耐心解读。

课程推荐

推荐之前我建议大家先是自己追几遍,再去看课程。

一定要有基础再去听,不然天书,有基础听的话就是在解惑icon-default.png?t=M1FBhttps://www.bilibili.com/video/BV1ee411p7rP

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员李哈

创作不易,希望能给与支持

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值