通过@MapperScan源码了解Spring自定义注解扫描器 - Go语言中文社区

通过@MapperScan源码了解Spring自定义注解扫描器


我们在使用springboot 整合MyBatis时,需要在启动类上添加上@MapperScan注解,并写入mapper接口的包路径,然后我们就能通过从spring IOC容器中取对应的mapper的Bean来进行持久化操作了,那么@MapperScan是如何将mapper接口实例化并注入到Spring IOC容器中的呢?

首先搭建一个spring boot项目,引入mybatis和mysql的相关maven包。在application.properties中配置好连接参数。这些基操我就不写了
新建mapper,当然我这里取名取成dao了,意思到了就行。

package com.p6spy.demop6spy.dao;

import org.apache.ibatis.annotations.Select;
public interface TestDao {
    @Select("select count(*) from subject_sae_detail")
    String getInfo();
}

启动类上添加注解,指定扫描com.p6spy.demop6spy.dao

@SpringBootApplication
@MapperScan({"com.p6spy.demop6spy.dao"})
public class Demop6spyApplication {

    public static void main(String[] args) {
        SpringApplication.run(Demop6spyApplication.class, args);
    }

}

就能在service实现类中注入TestDao 来进行持久化操作了。

一.@MapperScan

首先让我们来看下@MapperScan注解源码(这里为了清晰的了解扫描注入过程,所以我只选取了部分代码,其余的删除了)。

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

  String[] value() default {};

  String[] basePackages() default {};

  Class<? extends Annotation> annotationClass() default Annotation.class;

  Class<?> markerInterface() default Class.class;

  Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;
}
1.首先是@Import(MapperScannerRegistrar.class),从名字上看是一个扫描注册器,点击去看下,发现其继承于ImportBeanDefinitionRegistrar

主要作用是:

ImportBeanDefinitionRegistrar接口的作用是当这个接口的实现类(类A)被@Import接口引入某个被标记了@Configuration的注册类(类B)时,可以得到这个类(类B)的所有注解,然后做一些动态注册Bean的事儿。

我们看下@MapperScan,这也没有@Configuration啊,别急,因为@MapperScan是被启动类引用的,所以还要去看下启动类,虽然启动类看上去是只有@SpringBootApplication@MapperScan两个注解,实际上是这样的。
@SpringBootApplication如下

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })

@SpringBootConfiguration如下

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}

找到@Configuration了。

2 . valuebasePackages
	String[] value() default {};
  	String[] basePackages() default {};

这两行应该比较好辨认,就是需要我们填写的mapper接口所在的包路径信息。
就像这样写,后面我们会发现这俩填哪个都行,结果是一样的。
在这里插入图片描述
或者这样,这样写的话默认是赋值给value
在这里插入图片描述

3.annotationClass,在这里的作用是:配置了该注解的mapper才会被扫描器扫描,与basePackage是与的作用。默认值是Annotation.class
Class<? extends Annotation> annotationClass() default Annotation.class;
4.markerInterface,在这里的作用是:基于接口的过滤器,实现了该接口的mapper才会被扫描器扫描,与basePackage是与的作用。默认值是Class.class
Class<?> markerInterface() default Class.class;
5.构造工厂
Class<? extends MapperFactoryBean> factoryBean() default MapperFactoryBean.class;

二.MapperScannerRegistrar

在一中我们知道了实现ImportBeanDefinitionRegistrar的大致作用。

public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {

  private ResourceLoader resourceLoader;

  /**
   * {@inheritDoc}
   */
  @Override
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

    AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);

    // this check is needed in Spring 3.1
    if (resourceLoader != null) {
      scanner.setResourceLoader(resourceLoader);
    }

    Class<? extends Annotation> 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<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
    if (!BeanNameGenerator.class.equals(generatorClass)) {
      scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass));
    }

    Class<? extends MapperFactoryBean> 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<String> basePackages = new ArrayList<String>();
    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));
    }
    scanner.registerFilters();
    scanner.doScan(StringUtils.toStringArray(basePackages));
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void setResourceLoader(ResourceLoader resourceLoader) {
    this.resourceLoader = resourceLoader;
  }

}
1.首先在registerBeanDefinitions()方法第一行打个断点,看下是不是真的得到引用类的所有注解信息了

在这里插入图片描述
点开看下详细信息:可以看到只含有两个注解@SpringBootApplication@MapperScan,然后看下@MapperScan注解的value值,没错,也是我们填的值。
在这里插入图片描述

2.这里会通过getAnnotationAttributes方法获取MapperScan的所有信息,上面我们知道importingClassMetadata包含启动类的所有注解信息,所以这里获取MapperScan.class注解的信息也就很轻松了。
AnnotationAttributes annoAttrs = AnnotationAttributes
.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));

打个断点就能很清晰的看到annoAttrs 的值了:
在这里插入图片描述
这里我们能看到value的值就是我上面填的。

3.然后是比较重要的一点,新建扫描器scanner ,对mapper接口的扫描在这里实现。
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
4.然后是把一些参数赋给scanner扫描器。
	Class<? extends Annotation> 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);
    }
    ......

这里annotationClass对应的是只会扫描到带该注解的mapper,而markerInterface对应的是只会扫描到实现该接口的mapper。
因为我们并没有在启动器上的@MapperScan中添加annotationClassmarkerInterface 参数,所以这里会使用默认值Annotation.classClass.class,并且不会给scanner扫描器赋值,而之后scanner扫描器也就不会对扫描到的mapper进行过滤。

5.这里开始获取我们配置的要扫描的包了,可以看到会从valuebasePackagesbasePackageClasses中获取,所以我上面说结果是一样的了。
	List<String> basePackages = new ArrayList<String>();
    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));
    }
6.在这里scanner扫描器会开始扫描,并返回Bean定义集合(Set<BeanDefinitionHolder>)。
	scanner.registerFilters();
    scanner.doScan(StringUtils.toStringArray(basePackages));

在第一句scanner.registerFilters();中会用到上面3中设置的过滤参数,从方法名也可以看出来,这里会对扫描出的mapper设置过滤条件,只留下带有annotationClass 注解和实现了markerInterface接口的mapper,我们知道这里实际并没有给scanner赋这些值,所以相当于没有过滤这俩条件。

在第二句scanner.doScan(StringUtils.toStringArray(basePackages));中,会扫描出所有符合条件的mapper。

7.所有流程走完后会生成一个Bean定义集合(Set<BeanDefinitionHolder>,内含BeanDefinition),之后

BeanDefinitionRegistry接口(ApplicationContext会实现此接口)会将新的BeanDefinition注册到Spring IOC容器中。
主要实现方法

void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)

三.ClassPathMapperScanner扫描器

因为源码相对有点长,这里就不放所有的了,只截取重点部分说明,请自行查看所有源码。

1.首先看下继承关系,ClassPathMapperScanner 继承于ClassPathBeanDefinitionScanner
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner

熟悉Spring IOC容器的应该会知道ClassPathBeanDefinitionScanner,我们知道有多种方式将Bean注入IOC容器,可以通过在xml中配置<bean>,也可以通过@Bean注解,两种方式最终都会调用ClassPathBeanDefinitionScanner来实现扫描并生成BeanDefinition

2.registerFilters(),在上面二.MapperScannerRegistrar中有讲到是用来配置扫描过滤条件的,来看下源码:
  public void registerFilters() {
    boolean acceptAllInterfaces = true;

    // if specified, use the given annotation and / or marker interface
    if (this.annotationClass != null) {
      addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
      acceptAllInterfaces = false;
    }

    // override AssignableTypeFilter to ignore matches on the actual marker interface
    if (this.markerInterface != null) {
      addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {
        @Override
        protected boolean matchClassName(String className) {
          return false;
        }
      });
      acceptAllInterfaces = false;
    }

    if (acceptAllInterfaces) {
      // default include filter that accepts all classes
      addIncludeFilter(new TypeFilter() {
        @Override
        public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
          return true;
        }
      });
    }

    // exclude package-info.java
    addExcludeFilter(new TypeFilter() {
      @Override
      public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
        String className = metadataReader.getClassMetadata().getClassName();
        return className.endsWith("package-info");
      }
    });
  }

从上面可以看到会判断annotationClassmarkerInterface ,然后通过AnnotationTypeFilter()AssignableTypeFilter()来设置过滤器。如果这俩都没设置的话就会默认扫描所有的:

	addIncludeFilter(new TypeFilter() {
        @Override
        public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
          return true;
        }
      });

最后会过滤掉package-info.java

	// exclude package-info.java
    addExcludeFilter(new TypeFilter() {
      @Override
      public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
        String className = metadataReader.getClassMetadata().getClassName();
        return className.endsWith("package-info");
      }
    });
3.doScan(),终于到最重要的doScan方法了。看下源码:
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;
}

第一眼看上去是不是瞬间卧槽,怎么源码有点少,这里实际上是调用父类ClassPathBeanDefinitionScannerdoScan方法进行扫描(内容有点多=.=)。这里我们只要知道会对basePackages参数包含的所有包进行扫描,并且会使用之前设置的过滤器。最后返回扫描到的所有类——的BeanDefinitionHolder集合(当成类定义集合就行)。因为这里扫描的是Mybatis的mapper,我们知道mapper都是接口,而接口是无法实例化的,所以这样直接返回的话是没办法生成对应的实例的,就更不用说注入Spring IOC容器了。所以需要使用processBeanDefinitions方法再加工一下。

4.processBeanDefinitions
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 
                            
                            版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/yeyinglingfeng/article/details/98853848
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢