美文网首页
Spring 中的扫描

Spring 中的扫描

作者: 若琳丶 | 来源:发表于2021-02-06 22:35 被阅读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 的两个条件过滤器具有相当强的拓展性,它将扫描的包路径、判断条件以及其他参数全部可配,只要继承它,重写它的一些方法,就可以获取自己想要扫描到的类。

    相关文章

      网友评论

          本文标题:Spring 中的扫描

          本文链接:https://www.haomeiwen.com/subject/umhdtltx.html