Component Scan Configuration Class for Spring Source Analysis

Keywords: xml

After the bean definition is parsed, verify that there are parsing errors and create configuration class reading class

do {
  parser.parse(candidates);
  parser.validate();

  Set<ConfigurationClass> configClasses = new LinkedHashSet<ConfigurationClass>(parser.getConfigurationClasses());
  configClasses.removeAll(alreadyParsed);

  // Read the model and create bean definitions based on its content
  if (this.reader == null) {
    this.reader = new ConfigurationClassBeanDefinitionReader(
        registry, this.sourceExtractor, this.resourceLoader, this.environment,
        this.importBeanNameGenerator, parser.getImportRegistry());
  }
  this.reader.loadBeanDefinitions(configClasses);
  alreadyParsed.addAll(configClasses);
}
while (!candidates.isEmpty());

Loading bean definitions from configuration classes

public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
  TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
  for (ConfigurationClass configClass : configurationModel) {
    loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
  }
}

Judging whether to skip configuration classes

private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass,
    TrackedConditionEvaluator trackedConditionEvaluator) {

  if (trackedConditionEvaluator.shouldSkip(configClass)) {
    String beanName = configClass.getBeanName();
    if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
      this.registry.removeBeanDefinition(beanName);
    }
    this.importRegistry.removeImportingClassFor(configClass.getMetadata().getClassName());
    return;
  }

  if (configClass.isImported()) {
    registerBeanDefinitionForImportedConfigurationClass(configClass);
  }
  for (BeanMethod beanMethod : configClass.getBeanMethods()) {
    loadBeanDefinitionsForBeanMethod(beanMethod);
  }
  loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
  loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}

Register Import Configuration Class

private void registerBeanDefinitionForImportedConfigurationClass(ConfigurationClass configClass) {
  AnnotationMetadata metadata = configClass.getMetadata();
  AnnotatedGenericBeanDefinition configBeanDef = new AnnotatedGenericBeanDefinition(metadata);

  ScopeMetadata scopeMetadata = scopeMetadataResolver.resolveScopeMetadata(configBeanDef);
  configBeanDef.setScope(scopeMetadata.getScopeName());
  String configBeanName = this.importBeanNameGenerator.generateBeanName(configBeanDef, this.registry);
  AnnotationConfigUtils.processCommonDefinitionAnnotations(configBeanDef, metadata);

  BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(configBeanDef, configBeanName);
  definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
  this.registry.registerBeanDefinition(definitionHolder.getBeanName(), definitionHolder.getBeanDefinition());
  configClass.setBeanName(configBeanName);

  if (logger.isDebugEnabled()) {
    logger.debug("Registered bean definition for imported class '" + configBeanName + "'");
  }
}

Register bean definitions derived from bean methods

private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) {
  ConfigurationClass configClass = beanMethod.getConfigurationClass();
  MethodMetadata metadata = beanMethod.getMetadata();
  String methodName = metadata.getMethodName();

  // Do we need to mark the bean as skipped by its condition?
  if (this.conditionEvaluator.shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN)) {
    configClass.skippedBeanMethods.add(methodName);
    return;
  }
  if (configClass.skippedBeanMethods.contains(methodName)) {
    return;
  }

  // Consider name and any aliases
  AnnotationAttributes bean = AnnotationConfigUtils.attributesFor(metadata, Bean.class);
  List<String> names = new ArrayList<String>(Arrays.asList(bean.getStringArray("name")));
  String beanName = (names.size() > 0 ? names.remove(0) : methodName);

  // Register aliases even when overridden
  for (String alias : names) {
    this.registry.registerAlias(beanName, alias);
  }

  // Has this effectively been overridden before (e.g. via XML)?
  if (isOverriddenByExistingDefinition(beanMethod, beanName)) {
    return;
  }

  ConfigurationClassBeanDefinition beanDef = new ConfigurationClassBeanDefinition(configClass, metadata);
  beanDef.setResource(configClass.getResource());
  beanDef.setSource(this.sourceExtractor.extractSource(metadata, configClass.getResource()));

  if (metadata.isStatic()) {
    // static @Bean method
    beanDef.setBeanClassName(configClass.getMetadata().getClassName());
    beanDef.setFactoryMethodName(methodName);
  }
  else {
    // instance @Bean method
    beanDef.setFactoryBeanName(configClass.getBeanName());
    beanDef.setUniqueFactoryMethodName(methodName);
  }
  beanDef.setAutowireMode(RootBeanDefinition.AUTOWIRE_CONSTRUCTOR);
  beanDef.setAttribute(RequiredAnnotationBeanPostProcessor.SKIP_REQUIRED_CHECK_ATTRIBUTE, Boolean.TRUE);

  AnnotationConfigUtils.processCommonDefinitionAnnotations(beanDef, metadata);

  Autowire autowire = bean.getEnum("autowire");
  if (autowire.isAutowire()) {
    beanDef.setAutowireMode(autowire.value());
  }

  String initMethodName = bean.getString("initMethod");
  if (StringUtils.hasText(initMethodName)) {
    beanDef.setInitMethodName(initMethodName);
  }

  String destroyMethodName = bean.getString("destroyMethod");
  if (destroyMethodName != null) {
    beanDef.setDestroyMethodName(destroyMethodName);
  }

  // Consider scoping
  ScopedProxyMode proxyMode = ScopedProxyMode.NO;
  AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(metadata, Scope.class);
  if (attributes != null) {
    beanDef.setScope(attributes.getString("value"));
    proxyMode = attributes.getEnum("proxyMode");
    if (proxyMode == ScopedProxyMode.DEFAULT) {
      proxyMode = ScopedProxyMode.NO;
    }
  }

  // Replace the original bean definition with the target one, if necessary
  BeanDefinition beanDefToRegister = beanDef;
  if (proxyMode != ScopedProxyMode.NO) {
    BeanDefinitionHolder proxyDef = ScopedProxyCreator.createScopedProxy(
        new BeanDefinitionHolder(beanDef, beanName), this.registry, proxyMode == ScopedProxyMode.TARGET_CLASS);
    beanDefToRegister = new ConfigurationClassBeanDefinition(
        (RootBeanDefinition) proxyDef.getBeanDefinition(), configClass, metadata);
  }

  if (logger.isDebugEnabled()) {
    logger.debug(String.format("Registering bean definition for @Bean method %s.%s()",
        configClass.getMetadata().getClassName(), beanName));
  }

  this.registry.registerBeanDefinition(beanName, beanDefToRegister);
}

Loading bean definitions from imported resources

private void loadBeanDefinitionsFromImportedResources(
    Map<String, Class<? extends BeanDefinitionReader>> importedResources) {

  Map<Class<?>, BeanDefinitionReader> readerInstanceCache = new HashMap<Class<?>, BeanDefinitionReader>();

  for (Map.Entry<String, Class<? extends BeanDefinitionReader>> entry : importedResources.entrySet()) {
    String resource = entry.getKey();
    Class<? extends BeanDefinitionReader> readerClass = entry.getValue();

    // Default reader selection necessary?
    if (BeanDefinitionReader.class == readerClass) {
      if (StringUtils.endsWithIgnoreCase(resource, ".groovy")) {
        // When clearly asking for Groovy, that's what they'll get...
        readerClass = GroovyBeanDefinitionReader.class;
      }
      else {
        // Primarily ".xml" files but for any other extension as well
        readerClass = XmlBeanDefinitionReader.class;
      }
    }

    BeanDefinitionReader reader = readerInstanceCache.get(readerClass);
    if (reader == null) {
      try {
        // Instantiate the specified BeanDefinitionReader
        reader = readerClass.getConstructor(BeanDefinitionRegistry.class).newInstance(this.registry);
        // Delegate the current ResourceLoader to it if possible
        if (reader instanceof AbstractBeanDefinitionReader) {
          AbstractBeanDefinitionReader abdr = ((AbstractBeanDefinitionReader) reader);
          abdr.setResourceLoader(this.resourceLoader);
          abdr.setEnvironment(this.environment);
        }
        readerInstanceCache.put(readerClass, reader);
      }
      catch (Exception ex) {
        throw new IllegalStateException(
            "Could not instantiate BeanDefinitionReader class [" + readerClass.getName() + "]");
      }
    }

    // TODO SPR-6310: qualify relative path locations as done in AbstractContextLoader.modifyLocations
    reader.loadBeanDefinitions(resource);
  }
}

Loading bean definitions from the imported bean definition registry

private void loadBeanDefinitionsFromRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars) {
  for (Map.Entry<ImportBeanDefinitionRegistrar, AnnotationMetadata> entry : registrars.entrySet()) {
    entry.getKey().registerBeanDefinitions(entry.getValue(), this.registry);
  }
}

Determine whether the number of bean definitions after parsing is consistent with the previous one. If not, you need to parse the new bean definitions again. It is possible that these new bean definitions also contain the bean definition information that needs to be parsed.

candidates.clear();
if (registry.getBeanDefinitionCount() > candidateNames.length) {
  String[] newCandidateNames = registry.getBeanDefinitionNames();
  Set<String> oldCandidateNames = new HashSet<String>(Arrays.asList(candidateNames));
  Set<String> alreadyParsedClasses = new HashSet<String>();
  for (ConfigurationClass configurationClass : alreadyParsed) {
    alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
  }
  for (String candidateName : newCandidateNames) {
    if (!oldCandidateNames.contains(candidateName)) {
      BeanDefinition beanDef = registry.getBeanDefinition(candidateName);
      if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory) &&
          !alreadyParsedClasses.contains(beanDef.getBeanClassName())) {
        candidates.add(new BeanDefinitionHolder(beanDef, candidateName));
      }
    }
  }
  candidateNames = newCandidateNames;
}

Register ImportRegistry as a bean to support the ImportAware@Configuration class, and finally clear the cache

// Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes
if (singletonRegistry != null) {
  if (!singletonRegistry.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
    singletonRegistry.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
  }
}

if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
  ((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
}

The bean factory post-processing method needs to process the bean definition information of the bean factory that was not processed by the previous method postProcessBean Definition Registry.

public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
  int factoryId = System.identityHashCode(beanFactory);
  if (this.factoriesPostProcessed.contains(factoryId)) {
    throw new IllegalStateException(
        "postProcessBeanFactory already called on this post-processor against " + beanFactory);
  }
  this.factoriesPostProcessed.add(factoryId);
  if (!this.registriesPostProcessed.contains(factoryId)) {
    // BeanDefinitionRegistryPostProcessor hook apparently not supported...
    // Simply call processConfigurationClasses lazily at this point then.
    processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);
  }
  enhanceConfigurationClasses(beanFactory);
}

Then add the configuration class, and if the @Configuration class is proxyed, always proxy the target class

public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
  Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<String, AbstractBeanDefinition>();
  for (String beanName : beanFactory.getBeanDefinitionNames()) {
    BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
    if (ConfigurationClassUtils.isFullConfigurationClass(beanDef)) {
      if (!(beanDef instanceof AbstractBeanDefinition)) {
        throw new BeanDefinitionStoreException("Cannot enhance @Configuration bean definition '" +
            beanName + "' since it is not stored in an AbstractBeanDefinition subclass");
      }
      else if (logger.isWarnEnabled() && beanFactory.containsSingleton(beanName)) {
        logger.warn("Cannot enhance @Configuration bean definition '" + beanName +
            "' since its singleton instance has been created too early. The typical cause " +
            "is a non-static @Bean method with a BeanDefinitionRegistryPostProcessor " +
            "return type: Consider declaring such methods as 'static'.");
      }
      configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef);
    }
  }
  if (configBeanDefs.isEmpty()) {
    // nothing to enhance -> return immediately
    return;
  }
  ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer();
  for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) {
    AbstractBeanDefinition beanDef = entry.getValue();
    // If a @Configuration class gets proxied, always proxy the target class
    beanDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
    try {
      // Set enhanced subclass of the user-specified bean class
      Class<?> configClass = beanDef.resolveBeanClass(this.beanClassLoader);
      Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);
      if (configClass != enhancedClass) {
        if (logger.isDebugEnabled()) {
          logger.debug(String.format("Replacing bean definition '%s' existing class '%s' with " +
              "enhanced class '%s'", entry.getKey(), configClass.getName(), enhancedClass.getName()));
        }
        beanDef.setBeanClass(enhancedClass);
      }
    }
    catch (Throwable ex) {
      throw new IllegalStateException("Cannot load configuration class: " + beanDef.getBeanClassName(), ex);
    }
  }
}

 

Posted by niekos on Fri, 11 Oct 2019 12:29:19 -0700