Spring整合mybatis原理分析

      原生的mybatis的使用方式,是通过获取SqlSession,然后使用SqlSession去获取代理对象,执行sql,进行返回数据的包装返回的,具体实现可以参考mybatis的源码,简单使用如下所示:

public class MybatisOriginalDemo {

    public static void main(String[] args) {

        Reader reader = null;
        try {
            reader = Resources.getResourceAsReader("mybatis/mybatis-config.xml");
        } catch (IOException e) {
            e.printStackTrace();
        }


        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);

        SqlSession sqlSession = sqlSessionFactory.openSession();

        QuestionMapper mapper = sqlSession.getMapper(QuestionMapper.class);


        QuestionDO questionDO = mapper.selectById(10L);

        System.out.println(questionDO);


    }



}

那么Spring是如何和mybatis整合的呢?今天我们来一探究竟

我们在Spring应用中使用mybatis的时候,需要配置三个关键的bean如下:

1 dataSource

<!-- 配置数据源-->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="${driver}"/>
    <property name="url" value="${url}"/>
    <property name="username" value="${username}"/>
    <property name="password" value="${password}"/>
</bean>

2 SqlSessionFactoryBean

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="mapperLocations" value="spring/mapper/*/*Mapper.xml"/>
</bean>

3 MapperScannerConfigurer

<bean  class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <!--注意这里对于不同的数据源需要使用不同的包,否则会导致数据源映射错误 -->
    <property name="basePackage" value="com.muxi.sample.spring.dao"/>
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
</bean>

从上面的配置我们可以看出,其实dataSource 和 SqlSessionFactoryBean这两个的bean 是为mybatis配置的,而Spring整合mybatis关键的bean是MapperScannerConfigurer,那么这个bean到底是啥?拥有那么超级能力呢?能够这么完美的整合到Spring中,下面我们就解开它的神秘面纱

public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware

我们看一下类继承结构

从上面的类图我们可以看到MapperScannerConfigurer 实际上是一个BeanDefinitionRegistryPostProcessor,这个bean可以实现向Spring中注入BeanDefinition,那下面我们看看那是怎么注入BeanDefinition

关键代码:org.mybatis.spring.mapper.MapperScannerConfigurer#postProcessBeanDefinitionRegistry在这个方法中,从代码中我们可以看到这创建了一个mybatis自定义的一个扫描器

@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
  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();
  scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}

那我们继续看这个扫描器是怎么工作的?我们继续往下看代码

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
   Assert.notEmpty(basePackages, "At least one base package must be specified");
   Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
   for (String basePackage : basePackages) {
      Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
      for (BeanDefinition candidate : candidates) {
         ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
         candidate.setScope(scopeMetadata.getScopeName());
         String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
         if (candidate instanceof AbstractBeanDefinition) {
            postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
         }
         if (candidate instanceof AnnotatedBeanDefinition) {
            AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
         }
         if (checkCandidate(beanName, candidate)) {
            BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
            definitionHolder =
                  AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
            beanDefinitions.add(definitionHolder);
            registerBeanDefinition(definitionHolder, this.registry);
         }
      }
   }
   return beanDefinitions;
}

从上面的代码可以看出,这里就是通过你配置的包路径,将该路径下的Java接口扫描为一个BeanDefinition并包装为BeanDefinitionHolder,看到这里就有点疑惑了,Spring 默认是不会对接口进行解析的,所以接口是无法注册到Spring容器中成为一个单例的bean的,这里就算扫描成了一个BeanDefinition后面也无法完成实例化的呀?

带着这样的疑惑,我们继续往下看

继续跟代码我们发现其实scanner的真正实现是ClassPathMapperScanner

那么在ClassPathMapperScanner扫描完BeanDefinition后,有一步后置处理如下:

@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
  Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);

  if (beanDefinitions.isEmpty()) {
    logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
  } else {
    // 关键实现就在这个方法里
    processBeanDefinitions(beanDefinitions);
  }

  return beanDefinitions;
}

从代码中可以看出关键实现就在processBeanDefinitions()这个方法里了,那我们继续

private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
  GenericBeanDefinition definition;
  for (BeanDefinitionHolder holder : beanDefinitions) {
    definition = (GenericBeanDefinition) holder.getBeanDefinition();
    definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
    // 这里mybatis他偷偷的将beanClass的给换了
    definition.setBeanClass(this.mapperFactoryBean.getClass());

    definition.getPropertyValues().add("addToConfig", this.addToConfig);
    这里省略了无关的代码
    ...........
  }
}

看到没有就在这里,就是在这里mybatis来了个狸猫换太子,将beanClass偷偷的换成了mapperFactoryBean这个FactoryBean,这样就能够实现接口注入Spring中了,并能够获取到mybatis的代理对象了,是不是很惊叹‼️

我只能膜拜呀,大佬终究是大佬哈

这里省略了无关的代码

好了,到这里mybatis整合Spring 的解释清楚了,记录一下整个最终流程,还是蛮有意思的。

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值