Spring 中的扫描

2021-02-06  本文已影响0人  若琳丶

前言

spring 是如何去扫描的
以 springboot + mybatis 为例,看一下是如何扫描的
代码结构:
不使用 @MapperScanner 注解,使用 @Mapper 注解

我们的关注点在扫描的过程,至于扫描的前因后果,我们在其它文章中进行探究。

一、Spring 中的包扫描

类结构:
ClassPathScanningCandidateComponentProvider
ClassPathBeanDefinitionScanner

1.1 ComponentScanAnnotationParser

Spring 在扫描组件是在 ComponentScanAnnotationParser.parse() 方法中。它的前因过程,大致为:

  1. ConfigurationClassBeanDefinitionReader 在解析 ConfigurationClass 时,需要解析 @ComponentScan 注解
  2. 创建一个 ComponentScanAnnotationParser 对象,并将 @ComponentScan 注解作为参数,进行解析
  3. 处理扫描到的 beanDefinition 集合,对每一个 beanDefinition 在进行处理

扫描的起点就在 ComponentScanAnnotationParser.parse() 中,它的大体流程为:

  1. 创建 Scnanner
  2. 给 scanner 设置参数
  3. 执行扫描
    public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {

        /**
         * step 1: 创建 ClassPathBeanDefinitionScanner
         */
        ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
                componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);

        /**
         * step 2:从 @ComponentScan 注解中获取需要扫描的信息,并设置到 scanner 中,以备最后执行扫描
         */
        Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator");
        boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass);
        scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator :
                BeanUtils.instantiateClass(generatorClass));

        ScopedProxyMode scopedProxyMode = componentScan.getEnum("scopedProxy");
        if (scopedProxyMode != ScopedProxyMode.DEFAULT) {
            scanner.setScopedProxyMode(scopedProxyMode);
        }
        else {
            Class<? extends ScopeMetadataResolver> resolverClass = componentScan.getClass("scopeResolver");
            scanner.setScopeMetadataResolver(BeanUtils.instantiateClass(resolverClass));
        }

        // 设置需要扫描的资源通配符,默认 resourcePattern -> **/*.class
        scanner.setResourcePattern(componentScan.getString("resourcePattern"));

        // 设置【包含过滤】和【排除过滤】,他们作为扫描后是否进行
        for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) {
            for (TypeFilter typeFilter : typeFiltersFor(filter)) {
                scanner.addIncludeFilter(typeFilter);
            }
        }
        for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) {
            for (TypeFilter typeFilter : typeFiltersFor(filter)) {
                scanner.addExcludeFilter(typeFilter);
            }
        }

        // 设置是否需要延迟加载
        boolean lazyInit = componentScan.getBoolean("lazyInit");
        if (lazyInit) {
            scanner.getBeanDefinitionDefaults().setLazyInit(true);
        }

        Set<String> basePackages = new LinkedHashSet<>();
        
        // 需要扫描的包路径
        String[] basePackagesArray = componentScan.getStringArray("basePackages");
        for (String pkg : basePackagesArray) {
            String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
                    ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
            Collections.addAll(basePackages, tokenized);
        }
        for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
            basePackages.add(ClassUtils.getPackageName(clazz));
        }
        //如果设置的包路径为空,则默认使用当前配置类的包名
        if (basePackages.isEmpty()) {
            basePackages.add(ClassUtils.getPackageName(declaringClass));
        }

        scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) {
            @Override
            protected boolean matchClassName(String className) {
                return declaringClass.equals(className);
            }
        });
        
        /**
         * step 3:执行扫描
         */
        return scanner.doScan(StringUtils.toStringArray(basePackages));
    }

1.2、ClassPathBeanDefinitionScanner

ClassPathBeanDefinitionScanner 是 ClassPathScanningCandidateComponentProvider 的子类,
执行扫描的过程大致为:

  1. 获取到扫描到的 beanDefinition 集合
  2. 对每个 beanDefinition 设置参数
  3. 将 beanDefinition 都注册到 spring 中(最终都维护在 beanDefinitionMap 中)
/**
 * Perform a scan within the specified base packages,
 * returning the registered bean definitions.
 * <p>This method does <i>not</i> register an annotation config processor
 * but rather leaves this up to the caller.
 * @param basePackages the packages to check for annotated classes
 * @return set of beans registered if any for tooling registration purposes (never {@code null})
 */
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) {

        /**
         * step 1: 扫描包下所有的 beanDefinition 资源
         */
        Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
        
        /**
         * step 2: 处理 BeanDefinition 对象
         */
        for (BeanDefinition candidate : candidates) {
            ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
            candidate.setScope(scopeMetadata.getScopeName());
            String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
            
            //给 BeanDefinition 对象设置一些默认值
            //如:lazyInit、dependencyCheck 等
            if (candidate instanceof AbstractBeanDefinition) {
                postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
            }
            //如果是 AnnotatedBeanDefinition,则获取该类上面的 @Lazy、@Primary、@DependsOn 等注解,给 BeanDefinition 设置值
            if (candidate instanceof AnnotatedBeanDefinition) {
                AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
            }
            //判断 spring 中是否已经包含了当前 BeanDefinition,如果未包含,则进入
            if (checkCandidate(beanName, candidate)) {
                BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
                definitionHolder =
                        AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
                beanDefinitions.add(definitionHolder);
                
                /**
                 * step 3:将 BeanDefinition 注册到 spring 中
                 * 此处 registry 是spring 的 beanFactory
                 */
                registerBeanDefinition(definitionHolder, this.registry);
            }
        }
    }
    return beanDefinitions;
}

通过代码可以看到,实际完成扫描的并不是 ClassPathBeanDefinitionScanner,它仅仅是通过父类去获取 beanDefinition 的集合,它的核心在于对 beanDefinition 集合进行完善和注册。实际执行扫描工作的是它的父类 ClassPathScanningCandidateComponentProvider。

1.3、ClassPathScanningCandidateComponentProvider

ClassPathScanningCandidateComponentProvider 是 spring 中真正进行资源扫描的处理类,它的大致流程为:

  1. 通过包名和通配符,扫描所有目标资源
  2. 对每一个资源进行判断,符合条件的资源的添加到结果集合中
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
    Set<BeanDefinition> candidates = new LinkedHashSet<>();
    try {
        /**
         * step 1: 根据包路径扫描所有资源
         */
        String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                resolveBasePackage(basePackage) + '/' + this.resourcePattern;
        Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
        boolean traceEnabled = logger.isTraceEnabled();
        boolean debugEnabled = logger.isDebugEnabled();

        /**
         * step 2: 对每一个资源进行处理
         */
        for (Resource resource : resources) {
            if (traceEnabled) {
                logger.trace("Scanning " + resource);
            }
            if (resource.isReadable()) {
                try {
                    MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);

                    // 对当前资源进行条件过滤
                    if (isCandidateComponent(metadataReader)) {
                        // 创建为 ScannedGenericBeanDefinition 
                        ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
                        sbd.setSource(resource);
                        if (isCandidateComponent(sbd)) {
                            // ... 省略部分代码
                            candidates.add(sbd);
                        }
                        // ... 省略部分代码
                    }
                    // ... 省略部分代码
                }
                // ... 省略部分代码
            }
            else {
                if (traceEnabled) {
                    logger.trace("Ignored because not readable: " + resource);
                }
            }
        }
    }
    catch (IOException ex) {
        throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
    }
    return candidates;
}

对资源进行的判断是非常重要的一个点。
它要求参数中的类:只有 “不满足所有排除条件过滤器”,并且满足至少一个 “包含条件过滤器”。

/**
 * Determine whether the given class does not match any exclude filter
 * and does match at least one include filter.
 * @param metadataReader the ASM ClassReader for the class
 * @return whether the class qualifies as a candidate component
 */
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
    for (TypeFilter tf : this.excludeFilters) {
        if (tf.match(metadataReader, getMetadataReaderFactory())) {
            return false;
        }
    }
    for (TypeFilter tf : this.includeFilters) {
        if (tf.match(metadataReader, getMetadataReaderFactory())) {
            return isConditionMatch(metadataReader);
        }
    }
    return false;
}

二、Mybatis 中的 mapper 扫描

再来看看 mybatis 中对 mapper 的扫描处理。

类结构:
ClassPathScanningCandidateComponentProvider
ClassPathBeanDefinitionScanner
ClassPathMapperScanner

2.1 MapperScannerConfigurer

MapperScannerConfigurer 的 postProcessBeanDefinitionRegistry 方法是 mapper 扫描和处理的起点。
MapperScannerConfigurer 的前因流程,大致为:

  1. MapperScannerConfigurer 被 mybatis 中其他类加载入 spring 中
  2. 由于 MapperScannerConfigurer 是 BeanDefinitionRegistryPostProcessor,所以会在 spring 启动时,调用 postProcessBeanDefinitionRegistry 方法

与上方扫描所有组件类相同,它的大致流程也分为:

  1. 创建 Scanner
  2. 设置 Scanner 参数
  3. 执行扫描过程
@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.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
  if (StringUtils.hasText(lazyInitialization)) {
    scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
  }
  if (StringUtils.hasText(defaultScope)) {
    scanner.setDefaultScope(defaultScope);
  }
  scanner.registerFilters();
  scanner.scan(
      StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}

值得注意的是:
在第一部分,spring 用来扫描组件的 scanner 类为 ClassPathBeanDefinitionScanner;而此处 mybatis 用来扫描 mapper 的类为 ClassPathMapperScanner。ClassPathMapperScanner 为 ClassPathBeanDefinitionScanner 的子类。

2.2、ClassPathMapperScanner

public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
   /**
   * Calls the parent search that will search and register all the candidates. Then the registered objects are post
   * processed to set them as MapperFactoryBeans
   */
  @Override
  public Set<BeanDefinitionHolder> doScan(String... basePackages) {
      /**
       * step 1: 执行父类的扫描过程,获取包下所有的 beanDefinition
       * 此时获取到的 beanDefinition 都是 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 {
        /**
         * step 2: 处理扫描的结果
         */
      processBeanDefinitions(beanDefinitions);
    }
    return beanDefinitions;
  }
}

作为 ClassPathBeanDefinitionScanner 的子类,ClassPathMapperScanner 直接在父类将扫描到的 beanDefinition 设置默认参数、注册到 beanFactory 的基础上,再给 beanDefinition 进行属于 mybatis 自己特殊的处理。
处理过程为 processBeanDefinitions 方法。

2.2.1、Mybatis 中对于 mapper beanDefinition 的特殊处理

public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
    private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    AbstractBeanDefinition definition;
    BeanDefinitionRegistry registry = getRegistry();

    //遍历处理每一个 mapper beanDefinition
    for (BeanDefinitionHolder holder : beanDefinitions) {
      definition = (AbstractBeanDefinition) holder.getBeanDefinition();
      boolean scopedProxy = false;
      if (ScopedProxyFactoryBean.class.getName().equals(definition.getBeanClassName())) {
        definition = (AbstractBeanDefinition) Optional
            .ofNullable(((RootBeanDefinition) definition).getDecoratedDefinition())
            .map(BeanDefinitionHolder::getBeanDefinition).orElseThrow(() -> new IllegalStateException(
                "The target bean definition of scoped proxy bean not found. Root bean definition[" + holder + "]"));
        scopedProxy = true;
      }
      String beanClassName = definition.getBeanClassName();
      LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName
          + "' mapperInterface");

      // mapper 的接口是mapper类本身的类型,但是在 beanDefinition 中,它的实际 beanClass 是一个 FactoryBean。之后再获取 mapper 实例的时候,都是通过 FactoryBean 的 getObject 方法来获取的
      // 如:beanClassName:com.baowen.springdetail.mapper.UserMapper,mapperFactoryBeanClass:org.mybatis.spring.mapper.MapperFactoryBean
      definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
      definition.setBeanClass(this.mapperFactoryBeanClass);

      definition.getPropertyValues().add("addToConfig", this.addToConfig);

      // Attribute for MockitoPostProcessor
      // https://github.com/mybatis/spring-boot-starter/issues/475
      definition.setAttribute(FACTORY_BEAN_OBJECT_TYPE, beanClassName);

      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);

      if (scopedProxy) {
        continue;
      }

      if (ConfigurableBeanFactory.SCOPE_SINGLETON.equals(definition.getScope()) && defaultScope != null) {
        definition.setScope(defaultScope);
      }

      if (!definition.isSingleton()) {
        BeanDefinitionHolder proxyHolder = ScopedProxyUtils.createScopedProxy(holder, registry, true);
        if (registry.containsBeanDefinition(proxyHolder.getBeanName())) {
          registry.removeBeanDefinition(proxyHolder.getBeanName());
        }
        registry.registerBeanDefinition(proxyHolder.getBeanName(), proxyHolder.getBeanDefinition());
      }

    }
  }
}

代码比较冗长,其中的内容都是给 beanDefinition 设置属性。比较关键的属性是:

      // mapper 的接口是mapper类本身的类型,但是在 beanDefinition 中,它的实际 beanClass 是一个 FactoryBean。之后再获取 mapper 实例的时候,都是通过 FactoryBean 的 getObject 方法来获取的
      // 如:beanClassName:com.baowen.springdetail.mapper.UserMapper,mapperFactoryBeanClass:org.mybatis.spring.mapper.MapperFactoryBean
      definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
      definition.setBeanClass(this.mapperFactoryBeanClass);

它意味着 mapper 对象在获取实例的时候,是通过 FactoryBean 来间接进行创建对象的,这与存在于 spring 中普通的 bean 有所不同。
由于 mybatis 在代码定义的方式为仅仅定义 mapper 接口,没有实现类:

  1. 实际获取的 mapper 对象是动态代理类
  2. mapper 的动态代理生成对象过程,就在 mybatis 中的mapper 对应的 FactoryBean 的 getObject 方法中定义的

2.2.2、对于 @Mapper 注解的判断

由于本次整合 mybatis 的方式为用 @Mapper 注解标注在 mapper 接口之上,所以在扫描期间,判断资源是否有 @Mapper 注解,如果有,则确定就是 mybatis 所需要的 mapper,才会将其封装为 BeanDefinition,并保存在结果中进行返回。
此过程的关键点在于:在哪里对 @Mapper 注解进行的判断。

在第一部分 spring 扫描组件过程中,在 ClassPathScanningCandidateComponentProvider 中,维护了一个两个条件过滤器:一个是排除条件过滤器,一个是包含条件过滤器。由于 ClassPathMapperScanner 是 ClassPathBeanDefinitionScanner 的子类,所以也就是 ClassPathScanningCandidateComponentProvider 的子类。ClassPathMapperScanner 中重写了父类的 registerFilters 方法,在创建 ClassPathMapperScanner 对象去扫描 mapper 对象时,如果调用 registerFilters
方法,就会设置 mybatis 的扫描条件过滤器:

public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {

  /**
   * Configures parent scanner to search for the right interfaces. It can search for all interfaces or just for those
   * that extends a markerInterface or/and those annotated with the annotationClass
   */
  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((metadataReader, metadataReaderFactory) -> true);
    }

    // exclude package-info.java
    addExcludeFilter((metadataReader, metadataReaderFactory) -> {
      String className = metadataReader.getClassMetadata().getClassName();
      return className.endsWith("package-info");
    });
  }
}

ClassPathMapperScanner 有 this.annotationClass 属性,在创建 ClassPathMapperScanner 对象时,它的 annotationClass 属性会被设置为 @Mapper 注解类型。在设置条件过滤器时,添加了一个注解类型判断过滤器。

    if (this.annotationClass != null) {
      addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
      acceptAllInterfaces = false;
    }

在 ClassPathScanningCandidateComponentProvider 扫描到资源,并执行 isCandidateComponent 方法进行判断时,isCandidateComponent 方法中就会拿到这个包含条件过滤器进行判断。所以整个过程就是判断当前这个资源(类)上是否存在 @Mapper 注解。

三、总结

通过前两个部分源码的解析,可以了解到:
要想获取到目标的 beanDefinition 范围,进行的扫描过程有好几层处理:

  1. ClassPathScanningCandidateComponentProvider 中对所有资源的扫描、过滤和整合
  2. ClassPathBeanDefinitionScanner 对 beanDefinition 的完善
  3. ClassPathMapperScanner 对 beanDefinition 的特殊化处理

如果我们自己想要定义一个整合在 spring 中的轮子、定义我们自己的功能类时,完全可以像 mybatis 一样:

  1. 自定义注解
  2. 自定义一个 scanner 类,并继承于ClassPathBeanDefinitionScanner
  3. 创建一个 scanner 对象,并设置所需的条件过滤器和 annotationClass
  4. 进行扫描

spring 中 ClassPathScanningCandidateComponentProvider 的两个条件过滤器具有相当强的拓展性,它将扫描的包路径、判断条件以及其他参数全部可配,只要继承它,重写它的一些方法,就可以获取自己想要扫描到的类。

上一篇下一篇

猜你喜欢

热点阅读