mybatis 中接口注入spring源码分析

一、 springboot中使用mybatis

指定扫描mapper的包

这里只是简单的使用@MapperScan 注解,因为重点是分析Mapper对象注入到spring中,所以其他的配置,这里不介绍。

@MapperScan(value = { "com.card.mapper"})
@SpringBootApplication
public class TradeApplication {
    public static void main(String[] args) {
        System.setProperty("dubbo.application.logger","slf4j");
        SpringApplication.run(TradeApplication.class,args);
    }
}

按道理没有其他毛病的话,包com.card.mapper下的Mapper对象应该就会注入spring中。

mybatis 中接口注入spring源码分析_第1张图片
image.png

二、注入源码分析

这里涉及到的类先声明下
MapperScannerRegistrar:主要作用将@MapperScan中信息,配置到ClassPathMapperScanner中。
ClassPathMapperScanner:担任扫描指定包下的Mapper,以及将Mapper注入到spring的功能。

MapperScannerRegistrar

a. 调用时机

这个类调用时机是启动SpringBootxxxApplication类。

原因

xxxApplication中使用@MapperScan后,这个类(MapperScannerRegistrar)的registerBeanDefinitions方法会自动调用。这个机制的触发原因如下:

  1. 使用@ImportMapperScannerRegistrar导入。
@Import(MapperScannerRegistrar.class)
public @interface MapperScan {
  1. 实现ImportBeanDefinitionRegistrar 接口。
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar

b. 源码

/***

**/
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
  private ResourceLoader resourceLoader;
  @Override
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
  // 获取MapperScan 注解
    AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
  // 创建路径扫描器,下面的一大段都是将MapperScan 中的属性设置到ClassPathMapperScanner 
  // 做的就是一个set操作
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
    // this check is needed in Spring 3.1
    if (resourceLoader != null) {
    // 设置资源加载器,作用:扫描指定包下的class文件。
      scanner.setResourceLoader(resourceLoader);
    }
    Class annotationClass = annoAttrs.getClass("annotationClass");
    if (!Annotation.class.equals(annotationClass)) {
      scanner.setAnnotationClass(annotationClass);
    }
    Class markerInterface = annoAttrs.getClass("markerInterface");
    if (!Class.class.equals(markerInterface)) {
      scanner.setMarkerInterface(markerInterface);
    }
    Class generatorClass = annoAttrs.getClass("nameGenerator");
    if (!BeanNameGenerator.class.equals(generatorClass)) {
      scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass));
    }
    Class mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
    if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
      scanner.setMapperFactoryBean(BeanUtils.instantiateClass(mapperFactoryBeanClass));
    }
    scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
    scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));
    List basePackages = new ArrayList();
    for (String pkg : annoAttrs.getStringArray("value")) {
      if (StringUtils.hasText(pkg)) {
        basePackages.add(pkg);
      }
    }
    for (String pkg : annoAttrs.getStringArray("basePackages")) {
      if (StringUtils.hasText(pkg)) {
        basePackages.add(pkg);
      }
    }
    for (Class clazz : annoAttrs.getClassArray("basePackageClasses")) {
      basePackages.add(ClassUtils.getPackageName(clazz));
    }
    // 注册过滤器,作用:什么类型的Mapper将会留下来。
    scanner.registerFilters();
  // 扫描指定包
    scanner.doScan(StringUtils.toStringArray(basePackages));
  }
}

ClassPathMapperScanner

先来个这个类的继承代码

public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner

熟悉spring注入流程的同学应该对ClassPathBeanDefinitionScanner这个类有印象,其实这个类就是将bean注入spring中的核心入口。ClassPathBeanDefinitionScanner主要干了以下几件事。

  1. 扫描指定包下的class文件。
  2. 将包含@Component@ManagedBean@Named的类的元信息放入BeanDefinition对象中
  3. 判断BeanDefinition中的类元信息,将有效的放入spring
/**
**/
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
    private MapperFactoryBean mapperFactoryBean = new MapperFactoryBean();

...
  @Override
  public Set doScan(String... basePackages) {
   // 1. 扫描指定包下class文件
   //2.  注入spring
    Set 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;
  }
  private void processBeanDefinitions(Set beanDefinitions) {
    GenericBeanDefinition definition;
    for (BeanDefinitionHolder holder : beanDefinitions) {
      definition = (GenericBeanDefinition) holder.getBeanDefinition();
      ...
      //xxxMapper 的className
      definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); 
        ...
      // MapperFactoryBean 这个类很重要,下一节会提到。
      definition.setBeanClass(this.mapperFactoryBean.getClass());
      ...

    }
  }

  /**
   * 关键方法:就是因为这个方法的重写,所以mybatis中的Mapper接口能放到spring中。
  @returen true  接口&&独立 
   */
  @Override
  protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
    return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
  }
  /**
   * 
   */
  @Override
  protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) {
    if (super.checkCandidate(beanName, beanDefinition)) {
      return true;
    } else {
      logger.warn("Skipping MapperFactoryBean with name '" + beanName 
          + "' and '" + beanDefinition.getBeanClassName() + "' mapperInterface"
          + ". Bean already defined with the same name!");
      return false;
    }
  }

}
 
 

纵然已经解释了super.doScan方法,但是还是懵懂,我们看看这个方法。

//ClassPathBeanDefinitionScanner 
protected Set doScan(String... basePackages) {
        Assert.notEmpty(basePackages, "At least one base package must be specified");
        Set beanDefinitions = new LinkedHashSet();
        for (String basePackage : basePackages) {
                      //找到有效的BeanDefintion ,在这里其实就是Mapper
            Set candidates = findCandidateComponents(basePackage);
            for (BeanDefinition candidate : candidates) {
                ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
                candidate.setScope(scopeMetadata.getScopeName());
                       // 获取beanName
                String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
                if (candidate instanceof AbstractBeanDefinition) {
                    postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
                }
                if (candidate instanceof AnnotatedBeanDefinition) {
                    AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
                }
                  //校验beanDefintion是否已在spring工厂内。
                if (checkCandidate(beanName, candidate)) {
                    BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
                    definitionHolder =
                            AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
                    beanDefinitions.add(definitionHolder);
                         // 将对象注入spring工
                    registerBeanDefinition(definitionHolder, this.registry);
                }
            }
        }
        return beanDefinitions;
    }

super.doScan中最关键的还是findCandidateComponents这个方法,只要搞懂这个方法一切都揭开了。

//ClassPathBeanDefinitionScanner 
public Set findCandidateComponents(String basePackage) {
        Set candidates = new LinkedHashSet();
        try {
            String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                    resolveBasePackage(basePackage) + '/' + this.resourcePattern;
              // 资源加载器,找出指定包下的文件
            Resource[] resources = this.resourcePatternResolver.getResources(packageSearchPath);
            for (Resource resource : resources) {
                if (traceEnabled) {
                    logger.trace("Scanning " + resource);
                }
                if (resource.isReadable()) {
                    try {
                        MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(resource);
                           // 是否包含`@Component`,`@ManagedBean`,`@Named`
                        if (isCandidateComponent(metadataReader)) {
                            ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
                            sbd.setResource(resource);
                            sbd.setSource(resource);
                             // 这个就是子类重写的方法,如果不重写,接口无法放入spring中。
                            if (isCandidateComponent(sbd)) {
                                if (debugEnabled) {
                                    logger.debug("Identified candidate component class: " + resource);
                                }
                                candidates.add(sbd);
                
                        }
                    }
                    catch (Throwable ex) {
                        throw new BeanDefinitionStoreException(
                                "Failed to read candidate component class: " + resource, ex);
                    }
                }
                
                }
            }
        }
        catch (IOException ex) {
            throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
        }
        return candidates;
    }

总结

到这里mybatis中Mapper注入spring的源码已经分析完了。其实这个解决的问题还是如何将一个接口放入spring中。

你可能感兴趣的:(mybatis 中接口注入spring源码分析)