Bootstrap

SpringBoot与Mybatis整合源码深度解析

问题:

       SpringBoot中没有配置Mybatis的整合类,只有Mybatis的配置文件,那么是怎么进行关联的 ?

       在Mapper接口上不加@component或者是@repository也能将Mapper注入到Spring中,是如何实现的?

       所写的Mapper接口是如何关联到xml里面所写的sql语句的?

       如果对于上述三个问题的答案不是很清楚的话,可以往下面,本文将从源码的角度分析SpringBoot与Mybatis整合的源码,对于Spring和Mybatis的整合思想都是一样的,因为现在公司中大多数使用的是SpringBoot,所以选用SpringBoot与Mybatis整合作为分析的重点。

       SpringBoot与Mybatis整合体现在代码上是很简单,只需要三步就行:

       一.引入Mybatis启动包:

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>${mybatis.springboot.version}</version>
</dependency>

      二.在SpringBoot的启动类上面@MapperScan注解:

@MapperScan(value = "it.cast.wechat.mapper")
@SpringBootApplication
public class WechatApplication {
    public static void main(String[] args) {
        SpringApplication.run(WechatApplication.class, args);
    }
}

    三:在SpringBoot的application.properties配置文件中指定mybatis的配置文件和mapper的配置文件的路径:

mybatis.config-location = classpath:mybatis/SqlMapConfig.xml
mybatis.mapper-locations = classpath:mybatis/mapper/*.xml

   这样就指定Mapper接口、Mapper的xml文件和Mybatis配置文件的路径,这样就可以读取到文件,并进行解析了。

   下面就根据SpringBoot的启动流程进行解析

源码解析:

1.启动类

@MapperScan(value = "it.cast.wechat.mapper")
@SpringBootApplication
public class WechatApplication {
    public static void main(String[] args) {
        //1.传入自身的class类,启动SpringBoot项目
        SpringApplication.run(WechatApplication.class, args);
    }
}

      首先将自身的class类传入进行然后进行启动,这里牵扯到SpringBoot的启动流程,就不展开讲解了,说一下主要内容,其会在WechatApplication 变成一个BeanDefinition,然后放入到BeanFactory的BeanDefinitionNames集合中,然后在Spring的refresh方法中执行调用bean工厂的后置处理器时,将遍历BeanDefinitionNames集合,然后会找到包裹WechatApplication类的BeanDefinition,此时会解析其类中的注解信息,然后就可以买到@MapperScan注解了。其参考:Spring中bean的生命周期(最详细),而解析的关键类ConfigurationClassPostProcessor可以自行百度,以后有时间会分析一下这个类的源码。

2.@MapperScan注解

     @MapperScan注解,会使用@Import+ImportBeanDefinitionRegistrar接口的方法向容器中注册一个bean,关于@Import的源码分析可以参考:Spring中@Import注解源码分析,关于@MapperScan注解可以查看下面代码,仅给出重要的属性。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {

  String[] value() default {};

  String[] basePackages() default {};

  Class<?>[] basePackageClasses() default {};

}

3.MapperScannerRegistrar类

     MapperScannerRegistrar类实现了ImportBeanDefinitionRegistrar接口,所以可以使用@Import来向Spring容器中导出BeanDefinition。

public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {

  @Override
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
     //1.获取@MapperScan注解属性信息
    AnnotationAttributes mapperScanAttrs = AnnotationAttributes
        .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    if (mapperScanAttrs != null) {
      registerBeanDefinitions(mapperScanAttrs, registry, generateBaseBeanName(importingClassMetadata, 0));
    }
  }

  void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) {
    //2.使用BeanDefinitionBuilder来构造MapperScannerConfigurer的BeanDefinition对象
    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
    builder.addPropertyValue("processPropertyPlaceHolders", true);

    //3.从@MapperScan注解属性中获取设置的值,然后设置给MapperScannerConfigurer的bd
    Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
    if (!Annotation.class.equals(annotationClass)) {
      builder.addPropertyValue("annotationClass", annotationClass);
    }

    //忽略其他设置属性的代码

    List<String> basePackages = new ArrayList<>();
    basePackages.addAll(
        Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList()));
   
    //获取@MapperScan注解的属性值,然后添加到basePackages集合中
    basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("basePackages")).filter(StringUtils::hasText)
        .collect(Collectors.toList()));

    basePackages.addAll(Arrays.stream(annoAttrs.getClassArray("basePackageClasses")).map(ClassUtils::getPackageName)
        .collect(Collectors.toList()));

    //给BeanDefinition设置属性
    builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));
    //4.注册该MapperScannerConfigurer的bd
    registry.registerBeanDefinition(beanName, builder.getBeanDefinition());

  }
}

     上面可以总结为四步:

       1.获取@MapperScan注解上面的属性信息,因为@Import(MapperScannerRegistrar.class)是标注在@MapperScan上面的,所以第一步应该先获取@MapperScan注解的属性信息,这些属性信息使我们手动设置的,在WechatApplication类上使用@MapperScan,是这样使用的@MapperScan(value = "it.cast.wechat.mapper")。

       2.使用BeanDefinitionBuilder来构造MapperScannerConfigurer的BeanDefinition对象,对于Spring整合Mybatis熟悉的同学应该都知道,需要在Spring的XML文件中配置MapperScannerConfigurer对象,对于SpringBoot来说,这些都给你屏蔽了,不过其实现都是一样的。

       3.将@MapperScan注解属性中的值,设置到MapperScannerConfigurer的BeanDefinition对象上面,这是我们就可以拿到了在@MapperScan设置的value等值

       4.使用builder创建BeanDefinition,并放入Spring容器中。

4.MapperScannerConfigurer类

        由上图可知其MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口,我们知道BeanDefinitionRegistryPostProcessor接口是Spring中的一个扩展点,是的作用就是往Spring中注入BeanDefinition的,在Spring的refresh方法中执行调用bean工厂的后置处理器时进行执行。

        这里Spring在调用BeanDefinitionRegistryPostProcessor时,会调用三次,其调用先后顺序是这样的:

        1.如果类实现了BeanDefinitionRegistryPostProcessor,并且实现了PriorityOrdered

        2.如果类实现了BeanDefinitionRegistryPostProcessor,并且实现了Ordered

        3.如果类只实现了BeanDefinitionRegistryPostProcessor

        扫描WechatApplication类是在ConfigurationClassPostProcessor类中的postProcessBeanDefinitionRegistry方法进行的,其实现了BeanDefinitionRegistryPostProcessor,并且实现了PriorityOrdered,在扫描WechatApplication类是,会扫描其身上的注解信息,然后进行解析进行扫描类,此时扫描到@MapperScan注解,向Spring容器中注入MapperScannerConfigurer,然后当ConfigurationClassPostProcessor扫描完之后,接着会调用MapperScannerConfigurer的postProcessBeanDefinitionRegistry方法,进行扫描接口。

5.MapperScannerConfigurer类的postProcessBeanDefinitionRegistry方法

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
	if (this.processPropertyPlaceHolders) {
	  processPropertyPlaceHolders();
	}
	//1.构建一个ClassPathMapperScanner,并填充相应属性
	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.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
	if (StringUtils.hasText(lazyInitialization)) {
	  scanner.setLazyInitialization(Boolean.valu
;