Mybatis-Spring源码分析(一) MapperScan

56 篇文章 3 订阅
7 篇文章 1 订阅

前言

说到Mybatis想必大家都很熟悉,作为和Spring结合的非常紧密的一个数据库插件,其提供的便利性非常值得我们去学习。研读Mybatis-Spring的第一步就是要明白Mybatis是怎么把Mapper接口转化为MapperFactoryBean的,如果不了解这一步,基本上读源码也会读的知其然不知所以然。由于网上很多讲解MapperScan注解的都已经过时了,而Mybati-Spring的新版本已经对代码做了重大的修改,所以笔者根据比较新的mybatis-spring:2.0.5版本进行一个重新的解读,和大家一起看下新版中Mybatis-Spring做了什么样的更新。更多Spring内容进入【Spring解读系列目录】

@MapperScan注解

说到这个注解,最直观的印象就是它能把我们配置的包里面的Mapper接口转化为对应的代理类。其实笔者之前介绍Spring中ImportBeanDefinitionRegistrar类的时候已经对这一部分的原理有过详细的讲解,回顾点击这里【Spring框架中ImportBeanDefinitionRegistrar的应用】。但是Mybatis-Spring的新版本中有了一些大的更新,做了一个更加详细的封装,但是换汤不换药,所以如果对下面讲的Spring原理有疑惑的同学,可以去上面的帖子里补习一下。

@MapperScan注解源码解析

注解能够运行,是因为在@MapperScan上有一个更强大的注解@Import(MapperScannerRegistrar.class)MapperScannerRegistrar这个类结合Spring做了所有的工作,使得我们可以方便的使用。它所实现的ImportBeanDefinitionRegistrar接口中的registerBeanDefinitions()方法使得外部有机会去干扰Spring的运行,也给Mybatis的实现提供了机会。那么就进入这个类的registerBeanDefinitions()方法看看里面写了什么:

@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
	//首先拿到MapperScan注解的注解
  AnnotationAttributes mapperScanAttrs = AnnotationAttributes
      .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
  if (mapperScanAttrs != null) { //如果拿到了就进入自己的registerBeanDefinitions方法
    registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,
        generateBaseBeanName(importingClassMetadata, 0));
  }
}

相信看过Mybatis老版本源码的读者一定记得这个registerBeanDefinitions()方法里,将会使用doScan()方法扫描Mapper接口,并且做成MapperBeanFactor注册到Spring容器中,但是如今时代变了。

void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs,
    BeanDefinitionRegistry registry, String beanName) {
  //创建BeanDefinitionBuilder,拿出来MapperScannerConfigurer的BeanDefinition信息
  BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
  builder.addPropertyValue("processPropertyPlaceHolders", true);
  Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
  if (!Annotation.class.equals(annotationClass)) {
    builder.addPropertyValue("annotationClass", annotationClass);
  }
  Class<?> markerInterface = annoAttrs.getClass("markerInterface");
  if (!Class.class.equals(markerInterface)) {
    builder.addPropertyValue("markerInterface", markerInterface);
  }
  Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
  if (!BeanNameGenerator.class.equals(generatorClass)) {
    builder.addPropertyValue("nameGenerator", BeanUtils.instantiateClass(generatorClass));
  }
  Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
  if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
    builder.addPropertyValue("mapperFactoryBeanClass", mapperFactoryBeanClass);
  }
  String sqlSessionTemplateRef = annoAttrs.getString("sqlSessionTemplateRef");
  if (StringUtils.hasText(sqlSessionTemplateRef)) {
    builder.addPropertyValue("sqlSessionTemplateBeanName", annoAttrs.getString("sqlSessionTemplateRef"));
  }
  String sqlSessionFactoryRef = annoAttrs.getString("sqlSessionFactoryRef");
  if (StringUtils.hasText(sqlSessionFactoryRef)) {
    builder.addPropertyValue("sqlSessionFactoryBeanName", annoAttrs.getString("sqlSessionFactoryRef"));
  }
  List<String> basePackages = new ArrayList<>();
  basePackages.addAll(Arrays.stream(annoAttrs.getStringArray("value")).filter(StringUtils::hasText).collect(Collectors.toList()));
  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()));
  if (basePackages.isEmpty()) {
    basePackages.add(getDefaultBasePackage(annoMeta));
  }
  String lazyInitialization = annoAttrs.getString("lazyInitialization");
  if (StringUtils.hasText(lazyInitialization)) {
    builder.addPropertyValue("lazyInitialization", lazyInitialization);
  }
  builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));
  //被注册了
  registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
}

可以看到这个方法里已经不再有doScan()的方法,但是注意到最后一句发现最终被注册到Spring容器中的是一个builder的对象。往上翻发现这个builder是从MapperScannerConfigurer类中拿出来的BeanDefinition的信息。也就是说自此Spring正式接管了后续的创建过程,所以就必须去看下MapperScannerConfigurer类长什么样子:

public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, 
											InitializingBean, ApplicationContextAware, BeanNameAware {
	//......
}

熟悉Spring框架的同学,一定可以进来就发现一个老朋友BeanDefinitionRegistryPostProcessor。这个类是BeanFactoryPostProcessor子类,也就是通过实现这个接口我们能够获取并改变一个Bean的实例过程,也就是一个Bean的扩展点。那就直接找到他的实现方法postProcessBeanDefinitionRegistry()

@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);
  //设置MapperFactoryBeanClass
  scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
  if (StringUtils.hasText(lazyInitialization)) {
    scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
  }
  scanner.registerFilters();
  //扫描器
  scanner.scan(
      StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}

到了这里就有点意思了,新版本的Mybatis把自己的扫描类ClassPathMapperScanner给藏到了这里,其中有一点比较值得注意scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);给扫描出来的类设置一个MapperFactoryBean。点进去这个set方法:

public void setMapperFactoryBeanClass(Class<? extends MapperFactoryBean> mapperFactoryBeanClass) {
  this.mapperFactoryBeanClass = mapperFactoryBeanClass == null ? MapperFactoryBean.class : mapperFactoryBeanClass;
}

如果这个的mapperFactoryBeanClassnull,则把MapperFactoryBean.class赋值给它,也就是说这个属性必须是MapperFactoryBean类型的。这里先放一放,接着往下走去scanner.scan()方法:

public int scan(String... basePackages) {
   int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
   //喜闻乐见,终于找到亲人了
   doScan(basePackages);
   // Register annotation config processors, if necessary.
   if (this.includeAnnotationConfig) {
      AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
   }
   return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}

scan()方法里我们发现了老朋友doScan()方法,继续往里走:

@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
  //首先用父类的doScan()把所有指定包的mapper接口的BeanDefinition拿出来,
  //  不是主要部分,大家自己可以看是怎么转换的
  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 {
    //不是空就进行Bean的重定义
    processBeanDefinitions(beanDefinitions);
  }
  return beanDefinitions;
}

在这个方法里我们拿出来了所有MapperScan注解下指定的Mapper接口,并且将其传入了processBeanDefinitions(beanDefinitions)方法,继续进去:

private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
  GenericBeanDefinition definition;
  for (BeanDefinitionHolder holder : beanDefinitions) {
    definition = (GenericBeanDefinition) holder.getBeanDefinition();
    String beanClassName = definition.getBeanClassName();
    LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName
        + "' mapperInterface");
    definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
    /**************重中之重就是下面这行***********/
    definition.setBeanClass(this.mapperFactoryBeanClass); //极度关键
     /****************着重强调上面一行**************/
    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) {
      LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
      definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
    }
    definition.setLazyInit(lazyInitialization);
  }
}

这里对我们的Mapper进行了各种重定义,但是都不重要,最最重要的一句话就在这里definition.setBeanClass(this.mapperFactoryBeanClass);也就是说,如果我们定义的Mapper接口未来要表达的话,就使用MapperFactoryBean这个类进行。而这个类大有文章,这个类实现了FactoryBean接口,这样就能够通过Spring容器直接进行一个代理,并通过其中的getObject()方法拿到具体的代理对象(知识点回顾【实例区别BeanFactory和FactoryBean】),然后由代理对象处理具体的Sql语句的执行。

有什么好处

分析到这里就可以返回了,想一下为什么Mybatis要这么做,而不是直接在MapperScannerRegistrar#registerBeanDefinitions()里面继续直接使用doScan()?笔者目前考虑来说其实有三大好处。

  1. 符合编码的单一职责原则。这个方法既然是Spring定义的注册bean的方法,那么就只让这个方法执行注册的功能。
  2. 未来修改有优势。由于这里只是注册了bean,那么其开放程度就远远的大于原来的写法,如果用户想要对这个类进行修改那么,或者未来对这个类进行增强,那么修改的幅度会远远小于直接打开源代码修改。
  3. 解耦。减少对Spring框架的依赖,Mybatis只对Bean进行了注册,如果未来换了一个框架,Mybatis只需要修改注册的方式就可以完成对新框架的支持。

总结

通过分析源码可以得出@MapperScan主要做了三个工作:

  1. 扫描出所有的Mapper接口所对应的BeanDefinition。
  2. 把Mapper接口转换为FactoryBean,或者说MapperFactoryBean的BeanDefinition
  3. 为BeanDefinition添加一个构造方法的值,并使用这个Class,在Spring示例化过程中根据这个Class返回相对应的代理对象。

本篇博客解释了在新的版本中Mybatis是如何把Mapper接口转化为一个代理对象的过程,换句话说就是解决了Mapper接口到一个Bean的过程。下一篇【Mybatis-Spring源码分析(二) Mapper接口代理的生成】会着重讲解一下Mybatis再运行时是怎么使用代理去完成sql语句的执行。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值