Spring扫描类的原理

作为Java的开发者Spring可以称之为神一样的存在框架,好处太多无法用言语表达只能称之为Java排名的number one 框架。我们使用Spring它帮助我们实例化了很多Bean对象,但是这些Bean是怎样加载到Spring容器中的呢?相信很多人都不知道。现在就讲一下,还是以Spring Boot项目作为例子来讲,因为它的底层还是Spring。

Spring扫描类主要是依赖这个注解:

@ComponentScan

这个注解Spring boot项目已经自动给我们配置了,有了这个注解项目启动的时候就会找到这个注解然后进行类的加载。主要的代码就是在下面类的方法中完成:

ConfigurationClassParser.doProcessConfigurationClass()

@Nullable
  protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
      throws IOException {
    // Process any @ComponentScan annotations
    // 这个Set集合中就存放了我们项目中所以加了@ComponentScan类的相关信息
    Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
        sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
    if (!componentScans.isEmpty() &&
        !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
      for (AnnotationAttributes componentScan : componentScans) {
        // The config class is annotated with @ComponentScan -> perform the scan immediately
        // 这里就会进行扫描,得到BeanDefinition注册到Spring容器中。
        // 这里也就是扫描类的主要的方法
        Set<BeanDefinitionHolder> scannedBeanDefinitions =
            this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
        // Check the set of scanned definitions for any further config classes and parse recursively if needed
        for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
          BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
          if (bdCand == null) {
            bdCand = holder.getBeanDefinition();
          }
          // 这里会判断扫描出来的Bean是不是配置类【full 或者 lite模式的配置】
          if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
            parse(bdCand.getBeanClassName(), holder.getBeanName());
          }
        }
      }
    }
    // 扫描我们写的那些业务类主要是上面的方法,下面这些方法也是很重要比较难
    // 不是本文要讲的重点,当面下面这些真的很重要,很重要,很重要。
    // 因为不是主要讲的我就把后面代码删除了,留下英文注释,后面其他文章可能会讲。
    // Process any @Import annotations
    // Process any @ImportResource annotations
    // Process individual @Bean methods
    // Process default methods on interfaces
    processInterfaces(configClass, sourceClass);
    return null;
  }

 然后就会到这个方法中。

ComponentScanAnnotationParser. parse()

public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
    ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
        componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);

    Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator");
    boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass);
    // 设置BeanName的默认生成器
    scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator :
        BeanUtils.instantiateClass(generatorClass));
   // 获取要扫描哪些包下的类
    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));
    }
    return scanner.doScan(StringUtils.toStringArray(basePackages));
  }

如下我们没有配置扫描的包,默认取的包名,后面也就会扫描这个包下及其子包下符合条件的类。

然后就会到如下的方法中。

 ClassPathBeanDefinitionScanner.doScan()

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) {
    // 这个就是获取我们目标组件的方法。也就是获取BeanDefination
      Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
      for (BeanDefinition candidate : candidates) {
        ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
        candidate.setScope(scopeMetadata.getScopeName());
        String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
        if (candidate instanceof AbstractBeanDefinition) {
          postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
        }
        if (candidate instanceof AnnotatedBeanDefinition) {
          AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
        }
        if (checkCandidate(beanName, candidate)) {
          BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
          definitionHolder =
              AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
          beanDefinitions.add(definitionHolder);
          registerBeanDefinition(definitionHolder, this.registry);
        }
      }
    }
    return beanDefinitions;
  }
 
 //********************************重要***************//
   private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
    Set<BeanDefinition> candidates = new LinkedHashSet<>();
    try {
    // 获取实际的路径。【classpath*:io/renren/**/*.class】
      String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
          resolveBasePackage(basePackage) + '/' + this.resourcePattern;
      Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
    // 获取对应包下的所有的资源类【所有的类spring封装成了一个资源类】
      for (Resource resource : resources) {
        if (resource.isReadable()) {
          try {
          // 这个使用的ASM技术生成了元素读取器【这个里面有类的各种信息,注解信息也有】,
         // 一般我们可能会使用反射,但是Spring这里使用了一个高级的技术ASM
            MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
            if (isCandidateComponent(metadataReader)) {
              ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
              sbd.setResource(resource);
              sbd.setSource(resource);
              // 这里还有一个判断就是如果一个接口它根本无法实例化,
              // 如果接口也加了Component注解也会把它排除。
              if (isCandidateComponent(sbd)) {
                candidates.add(sbd);
              }
                 
    return candidates;
  } 
  
  // 判断是否是Spring的组件
  protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
    // 1:判断是否是要排除的类
    for (TypeFilter tf : this.excludeFilters) {
      if (tf.match(metadataReader, getMetadataReaderFactory())) {
        return false;
      }
    }
    // 2:符合includeFilters的会进行条件的匹配,通过了才是Bean
    // 也就是看有没有@Component这个注解
    for (TypeFilter tf : this.includeFilters) {
      if (tf.match(metadataReader, getMetadataReaderFactory())) {
        //这里还有一个条件注解的判断spring boot用的比较多
        return isConditionMatch(metadataReader);
      }
    }
    return false;
  }

 注意,我图片中的描述文字也是很重要的。获取的符合条件的类了,就把这些类封装成BeanDefination。然后Spring在根据这些BeanDefination去实例化Bean。【当然里面还有很多细节我没讲,因为我感觉了解到这些开发中遇到的相关的问题应该就很容易解决了。】具体什么是BeanDefination,可以仔细阅读我的这篇文章。

深入理解Spring的Bean定义对象BeanDefinition-面试重点_程序员xiaozhang的博客-CSDN博客

看了什么是BeanDefinition,后面就是实例化Bean了可以看我的这篇文章

Spring中Bean的实例化详细流程_bean的实例化过程_程序员xiaozhang的博客-CSDN博客

好了读完这三篇文章,我感觉你应该清楚的知道了我们写的一个类Spring是怎样给我们实例化成一个Bean的了。类的初始化主要就是先加载,转换为BeanDefinition,然后再实例化为Bean对象。希望这三篇文章对你理解Spring有所帮助。也欢迎你把这三篇文章分享一下,让更多的人看到。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值