Interpretation of ConfigurationClassPostProcessor class of Spring source code

catalogue

1. postProcessBeanDefinitionRegistry()

(1) @ Conditional annotation parsing

(2) Parsing of @ Component, @ ComponentScan, @ Import, @ ImportSource and other annotations

a. @Component, @ ComponentScan, @ Import, @ ImportSource resolution

b. @Configuration resolution

c. @ComponentScans parsing

d. Internal class processing

d. @PropertySource parsing

e. @Import parse

2. postProcessBeanFactory()

Now, when using the Spring framework, we basically inject beans based on annotations, such as @ PropertySource, @ ComponentScan, @ Bean, @ Import, @ ImportSource, @ Configuration and ImportSelector. Their resolution is inseparable from the ConfigurationClassPostProcessor class, These annotations are to scan (or Import) and put the scanned (or imported) classes into the Spring container to become BeanDefinition.

Previously, we registered Spring context based on XML. Now, annotation context has become popular, as follows:

AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(ScanBean.class);

Import package path through @ ComponentScan annotation:

@Component
@ComponentScan(value = "com.baec.kieasar")
public class ScanBean {
 
}

Or:

AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext("com.baec.kieasar");

Let's look at the constructor of AnnotationConfigApplicationContext:

public AnnotationConfigApplicationContext(DefaultListableBeanFactory beanFactory) {
   super(beanFactory);
   this.reader = new AnnotatedBeanDefinitionReader(this);
   this.scanner = new ClassPathBeanDefinitionScanner(this);
}

Enter the constructor of AnnotatedBeanDefinitionReader:

public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry) {
   this(registry, getOrCreateEnvironment(registry));
}

Enter this:

public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) {
   Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
   Assert.notNull(environment, "Environment must not be null");
   this.registry = registry;
   this.conditionEvaluator = new ConditionEvaluator(registry, environment, null);
   AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}

Enter annotationconfigutils Registerannotationconfigprocessors() method

Category: org springframework. context. annotation. AnnotationConfigUtils

public static void registerAnnotationConfigProcessors(BeanDefinitionRegistry registry) {
   registerAnnotationConfigProcessors(registry, null);
}

ConfigurationClassPostProcessor is registered here:

RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);

public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
			BeanDefinitionRegistry registry, @Nullable Object source) {

		DefaultListableBeanFactory beanFactory = unwrapDefaultListableBeanFactory(registry);
		if (beanFactory != null) {
			// Set the OrderComparator of beanFactory to annotationawarerder comparator
			// It is a comparator for sorting, such as new ArrayList < > () sort(Comparator)
			if (!(beanFactory.getDependencyComparator() instanceof AnnotationAwareOrderComparator)) {
				beanFactory.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE);
			}
			// The contextannotationautowirecandideresolver class is an annotation candidate parser (mainly dealing with @ Lazy), which is used to infer whether a Bean needs dependency injection
			if (!(beanFactory.getAutowireCandidateResolver() instanceof ContextAnnotationAutowireCandidateResolver)) {
				beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
			}
		}

		Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<>(8);

		// Register BeanDefinition of ConfigurationClassPostProcessor type
		if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
			RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
			def.setSource(source);
			beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
		}

		// Register the BeanDefinition of the AutowiredAnnotationBeanPostProcessor type
		if (!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) {
			RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class);
			def.setSource(source);
			beanDefs.add(registerPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
		}

		// Check for JSR-250 support, and if present add the CommonAnnotationBeanPostProcessor.
		// Register BeanDefinition of CommonAnnotationBeanPostProcessor type
		if (jsr250Present && !registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)) {
			RootBeanDefinition def = new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class);
			def.setSource(source);
			beanDefs.add(registerPostProcessor(registry, def, COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
		}

		// Check for JPA support, and if present add the PersistenceAnnotationBeanPostProcessor.
		// Register BeanDefinition of PersistenceAnnotationBeanPostProcessor type
		if (jpaPresent && !registry.containsBeanDefinition(PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME)) {
			RootBeanDefinition def = new RootBeanDefinition();
			try {
				def.setBeanClass(ClassUtils.forName(PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME,
						AnnotationConfigUtils.class.getClassLoader()));
			}
			catch (ClassNotFoundException ex) {
				throw new IllegalStateException(
						"Cannot load optional framework class: " + PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME, ex);
			}
			def.setSource(source);
			beanDefs.add(registerPostProcessor(registry, def, PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME));
		}

		// Register the BeanDefinition of EventListener methodprocessor type to process the @ EventListener annotation
		if (!registry.containsBeanDefinition(EVENT_LISTENER_PROCESSOR_BEAN_NAME)) {
			RootBeanDefinition def = new RootBeanDefinition(EventListenerMethodProcessor.class);
			def.setSource(source);
			beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_PROCESSOR_BEAN_NAME));
		}

		// Register the BeanDefinition of defaulteventlistener factory type to handle the @ EventListener annotation
		if (!registry.containsBeanDefinition(EVENT_LISTENER_FACTORY_BEAN_NAME)) {
			RootBeanDefinition def = new RootBeanDefinition(DefaultEventListenerFactory.class);
			def.setSource(source);
			beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_FACTORY_BEAN_NAME));
		}

		return beanDefs;
	}

First, let's take a look at the inheritance relationship of the ConfigurationClassPostProcessor class:

public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPostProcessor,
      PriorityOrdered, ResourceLoaderAware, ApplicationStartupAware, BeanClassLoaderAware, EnvironmentAware {

This class implements the BeanDefinitionRegistryPostProcessor interface to operate (register) BeanDefinition and add it to BeanDefinitionRegistry. The PriorityOrdered interface is used for sorting. It inherits the Ordered interface and overrides the getOrder() method:

@Override
// Set the execution sequence. The smaller the value, execute first
public int getOrder() {
   return Ordered.LOWEST_PRECEDENCE;  // within PriorityOrdered
}

1. postProcessBeanDefinitionRegistry()

At the same time, the BeanDefinitionRegistyPostProcessor interface is implemented. It is bound to implement the postProcessBeanDefinitionRegistry() method, register BeanDefinition and scan configuration classes. Let's look at this method first:

/**
 * Derive further bean definitions from the configuration classes in the registry.
 * This is the method of beanfactoryprocessor of BeanDefinitionRegistryPostProcessor interface, which has been rewritten
 */
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
   int registryId = System.identityHashCode(registry);
   if (this.registriesPostProcessed.contains(registryId)) {
      throw new IllegalStateException(
            "postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
   }
   if (this.factoriesPostProcessed.contains(registryId)) {
      throw new IllegalStateException(
            "postProcessBeanFactory already called on this post-processor against " + registry);
   }
   this.registriesPostProcessed.add(registryId);
   // TODO core logic parses configuration classes, focusing on ---- >
   processConfigBeanDefinitions(registry);
}

Enter the processConfigBeanDefinitions() method of this class:

/**
 * Build and validate a configuration model based on the registry of
 * {@link Configuration} classes.
 * In this method, all beanDefinition operations (parsing) are performed
 */
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
   List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
   // Get all beanNames
   String[] candidateNames = registry.getBeanDefinitionNames();

   for (String beanName : candidateNames) {
      BeanDefinition beanDef = registry.getBeanDefinition(beanName);
      // If there is this identifier, it will not be processed. "configurationClass" indicates the annotations, such as @ Configuration
      if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
         if (logger.isDebugEnabled()) {
            logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
         }
      }
      // Judge whether it is a configuration class and whether there is a BeanDefinition to be processed. If so, put it into configCandidates
      else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
         configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
      }
   }

   // Return immediately if no @Configuration classes were found
   // If the container is empty, that is, the @ Configuration class cannot be found, return immediately
   if (configCandidates.isEmpty()) {
      return;
   }

   // Sort by previously determined @Order value, if applicable
   // Sort the BeanDeanDefinition to be processed. The smaller the value, the higher the value
   configCandidates.sort((bd1, bd2) -> {
      int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
      int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
      return Integer.compare(i1, i2);
   });

   // Detect any custom bean name generation strategy supplied through the enclosing application context
   SingletonBeanRegistry sbr = null;
   if (registry instanceof SingletonBeanRegistry) {
      sbr = (SingletonBeanRegistry) registry;
      if (!this.localBeanNameGeneratorSet) {
         BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(
               AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);
         if (generator != null) {
            this.componentScanBeanNameGenerator = generator;
            this.importBeanNameGenerator = generator;
         }
      }
   }

   if (this.environment == null) {
      this.environment = new StandardEnvironment();
   }

   // Parsing configuration class, BeanDefinition parser -- ConfigurationClassParser
   // Parse each @Configuration class
   ConfigurationClassParser parser = new ConfigurationClassParser(
         this.metadataReaderFactory, this.problemReporter, this.environment,
         this.resourceLoader, this.componentScanBeanNameGenerator, registry);

   Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
   // Resolved
   Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
   do {
      StartupStep processConfig = this.applicationStartup.start("spring.context.config-classes.parse");
      // The core process of TODO analysis, importance: 5
      //  Collect first, such as @ Import, @ ComponentScan, @ Bean and ImportSelector, and finally package them into BeanDefinition
      // After parsing, the BeanDefinitionHolder will be encapsulated into ConfigurationClass
	  // In this process, steps such as scanning and importing will be performed to find other configurationclasses
      parser.parse(candidates);
      parser.validate();

      // ConfigurationClass is equivalent to all configuration classes (including itself) parsed up to now
      Set<ConfigurationClass> configClasses = new LinkedHashSet<>(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());
      }
      // TODO calls again. The parse() method above has not scanned some annotations, so it is called here.
      // After parsing, the BeanDefinitionHolder will be encapsulated into ConfigurationClass
	  // In this process, steps such as scanning and importing will be performed to find other configurationclasses
      this.reader.loadBeanDefinitions(configClasses);
      // The class that has been parsed is recursive again, and there is no need to repeat the processing
      alreadyParsed.addAll(configClasses);
      processConfig.tag("classCount", () -> String.valueOf(configClasses.size())).end();

      candidates.clear();
      // Compare the differences. If a new BeanDefiniton is found, go through the parsing process again
      if (registry.getBeanDefinitionCount() > candidateNames.length) {
         String[] newCandidateNames = registry.getBeanDefinitionNames();
         Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
         Set<String> alreadyParsedClasses = new HashSet<>();
         for (ConfigurationClass configurationClass : alreadyParsed) {
            alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
         }
         for (String candidateName : newCandidateNames) {
            if (!oldCandidateNames.contains(candidateName)) {
               BeanDefinition bd = registry.getBeanDefinition(candidateName);
               // Check whether the extra BeanDefinition is a configuration class and needs to be resolved
               if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
                     !alreadyParsedClasses.contains(bd.getBeanClassName())) {
                  candidates.add(new BeanDefinitionHolder(bd, candidateName));
               }
            }
         }
         candidateNames = newCandidateNames;
      }
   }
   while (!candidates.isEmpty());

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

   if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
      // Clear cache in externally provided MetadataReaderFactory; this is a no-op
      // for a shared cache since it'll be cleared by the ApplicationContext.
      ((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
   }
}

Enter parse() method:

Category: org springframework. context. annotation. ConfigurationClassParser

// Analytical process
public void parse(Set<BeanDefinitionHolder> configCandidates) {
   for (BeanDefinitionHolder holder : configCandidates) {
      BeanDefinition bd = holder.getBeanDefinition();
      try {
         // BeanDefinition obtained by scanning annotations
         if (bd instanceof AnnotatedBeanDefinition) {
             // Look at this method*****
            parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
         }
         // BeanDefinition not obtained by scanning annotations
         else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
            parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
         }
         else {
            parse(bd.getBeanClassName(), holder.getBeanName());
         }
      }
      catch (BeanDefinitionStoreException ex) {
         throw ex;
      }
      catch (Throwable ex) {
         throw new BeanDefinitionStoreException(
               "Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
      }
   }
    // TODO handles the resolution of the DeferredImportSelector interface. All current configuration classes are not executed until they are resolved
    // DeferredImportSelector means deferred ImportSelector. Normal ImportSelector is executed during the process of parsing configuration class
   this.deferredImportSelectorHandler.process();
}

Enter the parse() method of this class:

Category: org springframework. context. annotation. ConfigurationClassParser

protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
   // Encapsulate the metadata object and beanName into a ConfigurationClass object
   processConfigurationClass(new ConfigurationClass(metadata, beanName), DEFAULT_EXCLUSION_FILTER);
}

Enter the processConfigurationClass() method of this class. Here is the real parsing logic:

protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {
    // Support for @ Conditional annotation to filter out classes that do not need to be instantiated
   if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
      return;
   }

   ConfigurationClass existingClass = this.configurationClasses.get(configClass);
   if (existingClass != null) {
      if (configClass.isImported()) {
         // If a class is imported by two classes, it meets this condition and is added to the importBy attribute
         if (existingClass.isImported()) {
            existingClass.mergeImportedBy(configClass);
         }
         // Otherwise ignore new imported config class; existing non-imported class overrides it.
         return;
      }
      else {
         // Explicit bean definition found, probably replacing an import.
         // Let's remove the old one and go with the new one.
         this.configurationClasses.remove(configClass);
         this.knownSuperclasses.values().removeIf(configClass::equals);
      }
   }

   // Recursively process the configuration class and its superclass hierarchy.
   // This object can be understood as corresponding to the class or interface, and then wrap the metadata object in it
   SourceClass sourceClass = asSourceClass(configClass, filter);
   do {
      // TODO core code -- >
      sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
   }
   while (sourceClass != null);
    // After collection, put it into the Map
   this.configurationClasses.put(configClass, configClass);
}

(1) @ Conditional annotation parsing

First, the @ Conditional annotation is parsed. The @ Conditional annotation is widely used in Springboot to set the instantiation conditions of this class, such as:

@Conditional(value = {CustomCondition.class})
@Primary
@Bean
public Lison lison() {
    return new Lison();
}

The instantiation of Lison class needs to rely on CustomCondition or array. The dependent class CustomCondition needs to implement the Condition interface and the matches() method:

public class CustomCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        return true;
    }
}

Enter the @ Conditional annotation parsing method shouldSkip():

public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
   if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
      return false;
   }

   if (phase == null) {
      if (metadata instanceof AnnotationMetadata &&
            ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
         return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
      }
      return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
   }

   List<Condition> conditions = new ArrayList<>();
   // Gets the value of the @ Conditional annotation
   for (String[] conditionClasses : getConditionClasses(metadata)) {
      for (String conditionClass : conditionClasses) {
         // Reflection instance Condition object
         Condition condition = getCondition(conditionClass, this.context.getClassLoader());
         conditions.add(condition);
      }
   }
   // Sorting (support Order interface and @ Order annotation)
   AnnotationAwareOrderComparator.sort(conditions);

   for (Condition condition : conditions) {
      ConfigurationPhase requiredPhase = null;
      if (condition instanceof ConfigurationCondition) {
         requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
      }
      // Loop calls the matches() method of each condition
      if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
         return true;
      }
   }

   return false;
}

(2) Parsing of @ Component, @ ComponentScan, @ Import, @ ImportSource and other annotations

Go back to the processConfigurationClass() method and enter the core code doProcessConfigurationClass() method:

Category: org springframework. context. annotation. ConfigurationClassParser

 /*
  * TODO The annotation parsing core method SourceClass represents the encapsulation of the class. It is responsible for different directions. There is a metaData object in it
  */
@Nullable
protected final SourceClass doProcessConfigurationClass(
      ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)
      throws IOException {

   // Judge whether there is @ Component annotation on the class
   if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
      // Recursively process any member (nested) classes first
      // TODO recursively handles internal class processing with @ Component annotation
      processMemberClasses(configClass, sourceClass, filter);
   }

   // Process any @PropertySource annotations
   // Handling @ PropertySources and @ PropertySource annotations
   for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
         sourceClass.getMetadata(), PropertySources.class,
         org.springframework.context.annotation.PropertySource.class)) {
      if (this.environment instanceof ConfigurableEnvironment) {
         // TODO core logic - >
         processPropertySource(propertySource);
      }
      else {
         logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
               "]. Reason: Environment must implement ConfigurableEnvironment");
      }
   }

   // Process any @ComponentScan annotations
   // Process @ ComponentScans and @ ComponentScan annotations from sourceclass GetMetadata () gets the annotation and puts it into the Set container
   Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
         sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
   // Need to skip
   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
         // The logic in the parse() method under TODO is similar to the logic of < component scan > custom label resolution,
         // Scan through the ClassPathBeanDefinitionScanner to get the beandefinition and register it with the Spring container
         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
         // Here, after recursively scanning @ Component to generate beanDefinition, it returns to check whether there are special annotations on the class
         for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
            BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
            if (bdCand == null) {
               bdCand = holder.getBeanDefinition();
            }
            // Judge whether it is a candidate BeanDefinition. If so, parse() is a recursive process
            if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
               parse(bdCand.getBeanClassName(), holder.getBeanName());
            }
         }
      }
   }

   // Process any @Import annotations
   // TODO handles the @ Import annotation. The getImports(sourceClass) method is used to collect the @ Import annotation on the class and encapsulate it into a SourceClass
   processImports(configClass, sourceClass, getImports(sourceClass), filter, true);

   // Process any @ImportResource annotations
   // The @ ImportResource annotation has little effect, and the xml file is loaded
   AnnotationAttributes importResource =
         AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
   if (importResource != null) {
      String[] resources = importResource.getStringArray("locations");
      Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
      for (String resource : resources) {
         String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
         // Establish the mapping relationship between xml file and reader
         configClass.addImportedResource(resolvedResource, readerClass);
      }
   }

   // Process individual @Bean methods
   // (emphasis) process @ Bean annotations, collect methods with @ Bean annotations, and sourceClass is the current class
   Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
   for (MethodMetadata methodMetadata : beanMethods) {
      // Add to ConfigurationClass
      configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
   }

   // Process default methods on interfaces
   // Handle the interface corresponding to the @ Bean annotation method, and the logic is similar
   processInterfaces(configClass, sourceClass);

   // Process superclass, if any
   if (sourceClass.getMetadata().hasSuperClass()) {
      String superclass = sourceClass.getMetadata().getSuperClassName();
      if (superclass != null && !superclass.startsWith("java") &&
            !this.knownSuperclasses.containsKey(superclass)) {
         this.knownSuperclasses.put(superclass, configClass);
         // Superclass found, return its annotation metadata and recurse
         return sourceClass.getSuperClass();
      }
   }

   // No superclass -> processing is complete
   return null;
}

In the above method, we use a graph to summarize:

a. @Component, @ ComponentScan, @ Import, @ ImportSource resolution

For the judgment of @ Component, @ ComponentScan, @ Import, @ ImportSource annotations, in the isConfigurationCandidate() method:

Category: org springframework. context. annotation. ConfigurationClassUtils

	public static boolean isConfigurationCandidate(AnnotationMetadata metadata) {
		// Do not consider an interface or an annotation...
		if (metadata.isInterface()) {
			return false;
		}

		// Any of the typical annotations found?
		// If one of the @ Component, @ ComponentScan, @ Import, @ ImportSource annotations exists, it is the lite configuration class
		for (String indicator : candidateIndicators) {
			if (metadata.isAnnotated(indicator)) {
				return true;
			}
		}

		// Finally, let's look for @Bean methods...
		// If there is a @ Bean annotated method, it is the lite configuration class
		try {
			return metadata.hasAnnotatedMethods(Bean.class.getName());
		}
		catch (Throwable ex) {
			if (logger.isDebugEnabled()) {
				logger.debug("Failed to introspect @Bean methods on class [" + metadata.getClassName() + "]: " + ex);
			}
			return false;
		}
	}
abstract class ConfigurationClassUtils {

	public static final String CONFIGURATION_CLASS_FULL = "full";

	public static final String CONFIGURATION_CLASS_LITE = "lite";

	public static final String CONFIGURATION_CLASS_ATTRIBUTE =
			Conventions.getQualifiedAttributeName(ConfigurationClassPostProcessor.class, "configurationClass");

	private static final String ORDER_ATTRIBUTE =
			Conventions.getQualifiedAttributeName(ConfigurationClassPostProcessor.class, "order");


	private static final Log logger = LogFactory.getLog(ConfigurationClassUtils.class);

	private static final Set<String> candidateIndicators = new HashSet<>(8);

	static {
		candidateIndicators.add(Component.class.getName());
		candidateIndicators.add(ComponentScan.class.getName());
		candidateIndicators.add(Import.class.getName());
		candidateIndicators.add(ImportResource.class.getName());
	}

b. @Configuration resolution

Spring judges the @ Configuration annotated classes based on the following:

  • If there is a class with @ Configuration annotation and proxyBeanMethods is true or null (default), it is a Full Configuration class;
  • If there is @ Configuration annotation, but proxyBeanMethods is false, configure the class for lite;
  • If the @ Configuration annotation does not exist, but @ Component, @ ComponentScan, @ Import, @ ImportSource exist, it is the lite Configuration class;
  • If there is no @ Configuration annotation, but the method with @ Bean annotation is the lite Configuration class.

c. @ComponentScans parsing

A ClassPathBeanDefinitionScanner scanner will be created and the doScan() method will be called:

Category: org springframework. context. annotation. ClassPathBeanDefinitionScanner

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) {
      // Obtain the BeanDefinition according to the scanning path
      Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
      // Parse class
      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) {
            // Resolve @ lazy, @ Primary, @ DependsOn, @ Role, @ Description
            AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
         }
         // Check whether the beanName already exists in the Spring container
         if (checkCandidate(beanName, candidate)) {
            BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
            definitionHolder =
                  AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
            beanDefinitions.add(definitionHolder);
            // register
            registerBeanDefinition(definitionHolder, this.registry);
         }
      }
   }
   return beanDefinitions;
}

In the doProcessConfigurationClass() method, only the @ ComponentScan annotation is parsed, and the BeanDefinition will be generated by scanning. Other annotations are added to the configuration attribute of the ConfigurationClass class (not parsed at this time).

d. Internal class processing

Look at the processMemberClasses(configClass, sourceClass, filter) of this class, and handle those annotated with @ Component. This is the processing of internal classes:

private void processMemberClasses(ConfigurationClass configClass, SourceClass sourceClass,
      Predicate<String> filter) throws IOException {

   // Get the inner class of this class and wrap it into a sourceClass object. You can go in and have a look at the getMemberClasses() method
   Collection<SourceClass> memberClasses = sourceClass.getMemberClasses();
   if (!memberClasses.isEmpty()) {
      List<SourceClass> candidates = new ArrayList<>(memberClasses.size());
      for (SourceClass memberClass : memberClasses) {
         // If it needs to be processed, there are @ Bean, @ Component and other annotations
         if (ConfigurationClassUtils.isConfigurationCandidate(memberClass.getMetadata()) &&
               !memberClass.getMetadata().getClassName().equals(configClass.getMetadata().getClassName())) {
            candidates.add(memberClass);
         }
      }
      // sort
      OrderComparator.sort(candidates);
      // Loop through each inner class
      for (SourceClass candidate : candidates) {
         if (this.importStack.contains(configClass)) {
            this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
         }
         else {
            this.importStack.push(configClass);
            try {
               // Candidate is the child class, configClass is the parent class, and candidate is the internal class of configClass. It is processed recursively
               processConfigurationClass(candidate.asConfigClass(configClass), filter);
            }
            finally {
               this.importStack.pop();
            }
         }
      }
   }
}

ConfigurationClass is used to encapsulate the configuration class. Let's take a look at its member properties:

// This class is the wrapper of a class and the function of collecting annotations (including internal classes)
final class ConfigurationClass {

    // Basic source information in class, class, method and annotation information
    private final AnnotationMetadata metadata;
    // Encapsulation of stream in class
    private final Resource resource;
    
    @Nullable
    private String beanName;
    // Represents which class is imported, which is loaded with referenced or external classes
    private final Set<ConfigurationClass> importedBy = new LinkedHashSet<>(1);
    // If the class has @ Bean annotated methods, put them in this container
    private final Set<BeanMethod> beanMethods = new LinkedHashSet<>();
    // If the class has @ ImportedResources annotation, put it into this container
    private final Map<String, Class<? extends BeanDefinitionReader>> importedResources =
          new LinkedHashMap<>();
    // If the class implements the ImportBeanDefinitionRegistrar interface, put it in this container
    private final Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> importBeanDefinitionRegistrars =
          new LinkedHashMap<>();

d. @PropertySource parsing

Enter the processPropertySource(propertySource) method of this class, parse the configuration file and put it in the environment:

private void processPropertySource(AnnotationAttributes propertySource) throws IOException {
   String name = propertySource.getString("name");
   if (!StringUtils.hasLength(name)) {
      name = null;
   }
   String encoding = propertySource.getString("encoding");
   if (!StringUtils.hasLength(encoding)) {
      encoding = null;
   }
   // Get profile path
   String[] locations = propertySource.getStringArray("value");
   Assert.isTrue(locations.length > 0, "At least one @PropertySource(value) location is required");
   boolean ignoreResourceNotFound = propertySource.getBoolean("ignoreResourceNotFound");

   Class<? extends PropertySourceFactory> factoryClass = propertySource.getClass("factory");
   PropertySourceFactory factory = (factoryClass == PropertySourceFactory.class ?
         DEFAULT_PROPERTY_SOURCE_FACTORY : BeanUtils.instantiateClass(factoryClass));

   for (String location : locations) {
      try {
         // Replace placeholder
         String resolvedLocation = this.environment.resolveRequiredPlaceholders(location);
         // Load the configuration file as a stream and encapsulate it into a Resource object
         Resource resource = this.resourceLoader.getResource(resolvedLocation);
         // Load the configuration value property in the Resource, encapsulate it into a Properties object, and create a PropertySource object to add to the Environment
         addPropertySource(factory.createPropertySource(name, new EncodedResource(resource, encoding)));
      }
      catch (IllegalArgumentException | FileNotFoundException | UnknownHostException ex) {
         // Placeholders not resolvable or resource not found when trying to open it
         if (ignoreResourceNotFound) {
            if (logger.isInfoEnabled()) {
               logger.info("Properties location [" + location + "] not resolvable: " + ex.getMessage());
            }
         }
         else {
            throw ex;
         }
      }
   }
}

Composition of SourceClass:

private class SourceClass implements Ordered {

   private final Object source;  // Class or MetadataReader

   private final AnnotationMetadata metadata;

e. @Import parse

The @ Imprt annotation is used to import a class into the Spring container and instantiate it. For example:

@Component
// Although Import instantiates a class, the imported class can implement some interfaces, such as ImportSelector and ImportBeanDefinitionRegistar interfaces,
// Then we can call the methods in the interface, but if the class injected through the @ Component annotation implements these interfaces, it cannot call its methods.
@Import(DeferredImportSelectorDemo.class)
// @Import({DeferredImportSelectorDemo.class, LisonSelectImport.class, AwareBean.class})
@EnableAspectJAutoProxy
public class ImportBean {
}

The ImportBean in the example is annotated with @ Import. The parsing process is to wrap the ImportBean class into a SourceClass object, then get the @ Import annotation from the metadata method, get its value value, and put it into the Set container. The value value can also be an array and Import multiple classes.

public class LisonSelectImport implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        MergedAnnotations annotations = importingClassMetadata.getAnnotations();
        MergedAnnotation<EnableAspectJAutoProxy> eas = annotations.get(EnableAspectJAutoProxy.class);

        Object proxyTargetClass = eas.getValue("proxyTargetClass").get();
        // The fully qualified name of the class,
        return new String[]{Jack.class.getName()};
    }
}

At the same time, you can also get the annotation information on the imported class ImportBean, such as the annotation @ EnableAspectJAutoProxy to enable the aspect function. This is how Springboot automatic configuration is implemented.

Enter the core method processImports() for @ Import annotation parsing:

Category: this category org springframework. context. annotation. ConfigurationClassParser

private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
      Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,
      boolean checkForCircularImports) {

   // If there is no @ Import annotation, it will be returned directly and will not be processed
   if (importCandidates.isEmpty()) {
      return;
   }

   if (checkForCircularImports && isChainedImportOnStack(configClass)) {
      this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
   }
   else {
      this.importStack.push(configClass);
      try {
         // Every @ Import above the loop class
         for (SourceClass candidate : importCandidates) {
            // If an ImportSelector type is imported
            if (candidate.isAssignable(ImportSelector.class)) {
               // Candidate class is an ImportSelector -> delegate to it to determine imports
               Class<?> candidateClass = candidate.loadClass();
               // Reflection instantiation
               ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
                     this.environment, this.resourceLoader, this.registry);
               Predicate<String> selectorFilter = selector.getExclusionFilter();
               if (selectorFilter != null) {
                  exclusionFilter = exclusionFilter.or(selectorFilter);
               }
               // If it is DeferredImportSelector type
               if (selector instanceof DeferredImportSelector) {
                  // It's complicated. It's automatically configured in Springboot
                  this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
               }
               else {
                  // Call the ImportSelectors() method to return all the beannames that need to be imported into the Spring container. Here, you will call the implementation class method you wrote, and return the full qualified name of the class.
                  // The selectImports() method is called before collection.
                  String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
                  Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
                  // Recursive processing. Sometimes the class imported has @ Import annotation
                  processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
               }
            }
            // If the class of @ Import is of type ImportBeanDefinitionRegistrar
            else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
               // Candidate class is an ImportBeanDefinitionRegistrar ->
               // delegate to it to register additional bean definitions
               Class<?> candidateClass = candidate.loadClass();
               // Reflection instantiation
               ImportBeanDefinitionRegistrar registrar =
                     ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
                           this.environment, this.resourceLoader, this.registry);
               // Added to the ImportBeanDefinitionRegistrar container, registerBeanDefinitions() is not invoked here, and the method of calling the interface after collection is implemented.
               // addImportBeanDefinitionRegistrar() maintains the mapping relationship between an instance that implements the ImportBeanDefinitionRegistrar interface and the annotation metadata of this class
               configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
            }
            else {
               // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
               // process it as an @Configuration class
               // If it is not an ImportSelector or ImportBeanDefinitionRegistrar type, treat it as a @ Configuration class
               this.importStack.registerImport(
                     currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
               // If the imported class has other annotations, enter here and resolve it by recursive call
               processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
            }
         }
      }

Next, go back to the processConfigBeanDefinitions() method and look at this reader. Loadbeandefinitions () method, which is a method for processing logic and parsing annotations for @ Bean, @ ImportedResource and ImportBeanDefinitionRegistrar. After the configuration class is resolved, it will be added to the attribute corresponding to the current ConfigurationClass, and then it will be further resolved (or built) into BeanDefiniton.

public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {
   TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
   for (ConfigurationClass configClass : configurationModel) {
      // Annotation collection, encapsulating the class itself and its @ Bean annotated methods into BeanDefinition
      loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
   }
}

This method mainly parses the configuration class to generate BeanDefinition.

private void loadBeanDefinitionsForConfigurationClass(
      ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
   // Do you want to skip
   if (trackedConditionEvaluator.shouldSkip(configClass)) {
      String beanName = configClass.getBeanName();
      if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
         this.registry.removeBeanDefinition(beanName);
      }
      this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
      return;
   }

   // @The imported class and the internal class become BeanDefinition here
   if (configClass.isImported()) {
      registerBeanDefinitionForImportedConfigurationClass(configClass);
   }
   // @The method of Bean annotation becomes BeanDefinition
   for (BeanMethod beanMethod : configClass.getBeanMethods()) {
      loadBeanDefinitionsForBeanMethod(beanMethod);
   }
   // Process @ ImportResource annotation and xml parsing process
   loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
   // Handle the ImportBeanDefinitionRegistrar interface and call the registrerBeanDefinitions() method
   loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}

Enter loadBeanDefinitionsForBeanMethod():

Category: org springframework. context. annotation. ConfigurationClassBeanDefinitionReader

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

   AnnotationAttributes bean = AnnotationConfigUtils.attributesFor(metadata, Bean.class);
   Assert.state(bean != null, "No @Bean annotation attributes");

   // Consider name and any aliases
   List<String> names = new ArrayList<>(Arrays.asList(bean.getStringArray("name")));
   String beanName = (!names.isEmpty() ? 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 there is a duplicate @ Bean annotation method name (such as method overload), return and no new one will be generated
   if (isOverriddenByExistingDefinition(beanMethod, beanName)) {
      if (beanName.equals(beanMethod.getConfigurationClass().getBeanName())) {
         throw new BeanDefinitionStoreException(beanMethod.getConfigurationClass().getResource().getDescription(),
               beanName, "Bean name derived from @Bean method '" + beanMethod.getMetadata().getMethodName() +
               "' clashes with bean name for containing configuration class; please make those names unique!");
      }
      return;
   }

   // If it does not exist, a new BeanDefinition is generated
   ConfigurationClassBeanDefinition beanDef = new ConfigurationClassBeanDefinition(configClass, metadata);
   beanDef.setSource(this.sourceExtractor.extractSource(metadata, configClass.getResource()));

   if (metadata.isStatic()) {
      // static @Bean method
      if (configClass.getMetadata() instanceof StandardAnnotationMetadata) {
         beanDef.setBeanClass(((StandardAnnotationMetadata) configClass.getMetadata()).getIntrospectedClass());
      }
      else {
         beanDef.setBeanClassName(configClass.getMetadata().getClassName());
      }
      beanDef.setUniqueFactoryMethodName(methodName);
   }
   else {
      // instance @Bean method
      beanDef.setFactoryBeanName(configClass.getBeanName());
      beanDef.setUniqueFactoryMethodName(methodName);
   }

   if (metadata instanceof StandardMethodMetadata) {
      beanDef.setResolvedFactoryMethod(((StandardMethodMetadata) metadata).getIntrospectedMethod());
   }

   beanDef.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);
   beanDef.setAttribute(org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor.
         SKIP_REQUIRED_CHECK_ATTRIBUTE, Boolean.TRUE);

   AnnotationConfigUtils.processCommonDefinitionAnnotations(beanDef, metadata);

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

   boolean autowireCandidate = bean.getBoolean("autowireCandidate");
   if (!autowireCandidate) {
      beanDef.setAutowireCandidate(false);
   }

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

   String destroyMethodName = bean.getString("destroyMethod");
   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.isTraceEnabled()) {
      logger.trace(String.format("Registering bean definition for @Bean method %s.%s()",
            configClass.getMetadata().getClassName(), beanName));
   }
   // Register the parsed BeanDefiniton, and check whether overwriting is allowed here
   this.registry.registerBeanDefinition(beanName, beanDefToRegister);
}

protected boolean isOverriddenByExistingDefinition(BeanMethod beanMethod, String beanName) {
   if (!this.registry.containsBeanDefinition(beanName)) {
      return false;
   }
   BeanDefinition existingBeanDef = this.registry.getBeanDefinition(beanName);

   // Is the existing bean definition one that was created from a configuration class?
   // -> allow the current bean method to override, since both are at second-pass level.
   // However, if the bean method is an overloaded case on the same configuration class,
   // preserve the existing bean definition.
   // If BeanDefinition already exists in the beanName corresponding to @ Bean, set isFactoryMethodUnique of this BeanDefinition to false
   // Later, when you create a Bean based on this BeanDefinition, you will know that there is more than one Bean
   if (existingBeanDef instanceof ConfigurationClassBeanDefinition) {
      ConfigurationClassBeanDefinition ccbd = (ConfigurationClassBeanDefinition) existingBeanDef;
      if (ccbd.getMetadata().getClassName().equals(
            beanMethod.getConfigurationClass().getMetadata().getClassName())) {
         if (ccbd.getFactoryMethodMetadata().getMethodName().equals(ccbd.getFactoryMethodName())) {
            ccbd.setNonUniqueFactoryMethodName(ccbd.getFactoryMethodMetadata().getMethodName());
         }
         return true;
      }
      else {
         return false;
      }
   }

   // A bean definition resulting from a component scan can be silently overridden
   // by an @Bean method, as of 4.2...
   if (existingBeanDef instanceof ScannedGenericBeanDefinition) {
      return false;
   }

   // Has the existing bean definition bean marked as a framework-generated bean?
   // -> allow the current bean method to override it, since it is application-level
   if (existingBeanDef.getRole() > BeanDefinition.ROLE_APPLICATION) {
      return false;
   }

   // At this point, it's a top-level override (probably XML), just having been parsed
   // before configuration class processing kicks in...
   if (this.registry instanceof DefaultListableBeanFactory &&
         !((DefaultListableBeanFactory) this.registry).isAllowBeanDefinitionOverriding()) {
      throw new BeanDefinitionStoreException(beanMethod.getConfigurationClass().getResource().getDescription(),
            beanName, "@Bean definition illegally overridden by existing bean definition: " + existingBeanDef);
   }
   if (logger.isDebugEnabled()) {
      logger.debug(String.format("Skipping bean definition for %s: a definition for bean '%s' " +
            "already exists. This top-level bean definition is considered as an override.",
            beanMethod, beanName));
   }
   return true;
}

Enter isOverriddenByExistingDefinition() of this class to determine whether to repeat scanning:

protected boolean isOverriddenByExistingDefinition(BeanMethod beanMethod, String beanName) {
   if (!this.registry.containsBeanDefinition(beanName)) {
      return false;
   }
   BeanDefinition existingBeanDef = this.registry.getBeanDefinition(beanName);

   // Is the existing bean definition one that was created from a configuration class?
   // -> allow the current bean method to override, since both are at second-pass level.
   // However, if the bean method is an overloaded case on the same configuration class,
   // preserve the existing bean definition.
   // ConfigurationClassBeanDefinition is a BeanDefiniton parsed by @ Bean annotation method,
   // If BeanDefinition already exists in the beanName corresponding to @ Bean, set isFactoryMethodUnique of this BeanDefinition to false
   // Later, when you create a Bean based on this BeanDefinition, you will know that there is more than one Bean
   if (existingBeanDef instanceof ConfigurationClassBeanDefinition) {
      ConfigurationClassBeanDefinition ccbd = (ConfigurationClassBeanDefinition) existingBeanDef;
      if (ccbd.getMetadata().getClassName().equals(
            beanMethod.getConfigurationClass().getMetadata().getClassName())) {
         if (ccbd.getFactoryMethodMetadata().getMethodName().equals(ccbd.getFactoryMethodName())) {
            ccbd.setNonUniqueFactoryMethodName(ccbd.getFactoryMethodMetadata().getMethodName());
         }
         return true;
      }
      else {
         return false;
      }
   }

   // A bean definition resulting from a component scan can be silently overridden
   // by an @Bean method, as of 4.2...
   // ScannedGenericBeanDefinition is the BeanDefinition obtained after scanning
   if (existingBeanDef instanceof ScannedGenericBeanDefinition) {
      return false;
   }

   // Has the existing bean definition bean marked as a framework-generated bean?
   // -> allow the current bean method to override it, since it is application-level
   if (existingBeanDef.getRole() > BeanDefinition.ROLE_APPLICATION) {
      return false;
   }

   // At this point, it's a top-level override (probably XML), just having been parsed
   // before configuration class processing kicks in...
   if (this.registry instanceof DefaultListableBeanFactory &&
         !((DefaultListableBeanFactory) this.registry).isAllowBeanDefinitionOverriding()) {
      throw new BeanDefinitionStoreException(beanMethod.getConfigurationClass().getResource().getDescription(),
            beanName, "@Bean definition illegally overridden by existing bean definition: " + existingBeanDef);
   }
   if (logger.isDebugEnabled()) {
      logger.debug(String.format("Skipping bean definition for %s: a definition for bean '%s' " +
            "already exists. This top-level bean definition is considered as an override.",
            beanMethod, beanName));
   }
   return true;
}
  • @Bean annotated beans will overwrite the @ Component annotation;
  • If all are @ Bean annotated, overwrite;
  • @The Component annotation is repeated and an error is reported.

The previous method scanning and enhancement have been completed, and many beandefinitions have been obtained. However, the instantiated object of the Bean has not been generated, and the method corresponding to the @ Bean annotation has not been called. It will be called only when the Bean is created and instantiated and inferred from the constructor.  

2. postProcessBeanFactory()

Next, look at another method of the ConfigurationClassPostProcessor class, postProcessBeanFactory(), which is the method of the beanfactoryprocessor interface, which is equivalent to the post processor of BeanFactory. This method is rewritten here to generate a proxy for the class with @ Configuration annotation:

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

   // For Configuration classes annotated with @ Configuration, CGLIB proxy, bytecode enhancement
   enhanceConfigurationClasses(beanFactory);
   beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
}

Enter the enhanceConfigurationClasses() method:

Category: org springframework. context. annotation. ConfigurationClassPostProcessor

public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
   Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<>();
   // Traverse and find the configuration class according to beanName
   for (String beanName : beanFactory.getBeanDefinitionNames()) {
      BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
      Object configClassAttr = beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE);
      MethodMetadata methodMetadata = null;
      if (beanDef instanceof AnnotatedBeanDefinition) {
         methodMetadata = ((AnnotatedBeanDefinition) beanDef).getFactoryMethodMetadata();
      }
      if ((configClassAttr != null || methodMetadata != null) && beanDef instanceof AbstractBeanDefinition) {
         // Configuration class (full or lite) or a configuration-derived @Bean method
         // -> resolve bean class at this point...
         AbstractBeanDefinition abd = (AbstractBeanDefinition) beanDef;
         // If not, load the class
         if (!abd.hasBeanClass()) {
            try {
               abd.resolveBeanClass(this.beanClassLoader);
            }
            catch (Throwable ex) {
               throw new IllegalStateException(
                     "Cannot load configuration class: " + beanDef.getBeanClassName(), ex);
            }
         }
      }
      // Judge whether the configClassAttr attribute obtained by parsing the configuration class is full. If so, add the configBeanDefs container
      if (ConfigurationClassUtils.CONFIGURATION_CLASS_FULL.equals(configClassAttr)) {
         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.isInfoEnabled() && beanFactory.containsSingleton(beanName)) {
            logger.info("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;
   }

   // For the configuration class with full attribute, generate a proxy class and set it to BeanDefinition
   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);
      // Set enhanced subclass of the user-specified bean class
      Class<?> configClass = beanDef.getBeanClass();
      // Generate proxy object for configuration class
      Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);
      if (configClass != enhancedClass) {
         if (logger.isTraceEnabled()) {
            logger.trace(String.format("Replacing bean definition '%s' existing class '%s' with " +
                  "enhanced class '%s'", entry.getKey(), configClass.getName(), enhancedClass.getName()));
         }
         beanDef.setBeanClass(enhancedClass);
      }
   }
}

Enter the enhance() method:

Category: org springframework. context. annotation. ConfigurationClassEnhancer

public Class<?> enhance(Class<?> configClass, @Nullable ClassLoader classLoader) {
   if (EnhancedConfiguration.class.isAssignableFrom(configClass)) {
      if (logger.isDebugEnabled()) {
         logger.debug(String.format("Ignoring request to enhance %s as it has " +
               "already been enhanced. This usually indicates that more than one " +
               "ConfigurationClassPostProcessor has been registered (e.g. via " +
               "<context:annotation-config>). This is harmless, but you may " +
               "want check your configuration and remove one CCPP if possible",
               configClass.getName()));
      }
      return configClass;
   }
   Class<?> enhancedClass = createClass(newEnhancer(configClass, classLoader));
   if (logger.isTraceEnabled()) {
      logger.trace(String.format("Successfully enhanced %s; enhanced class name is: %s",
            configClass.getName(), enhancedClass.getName()));
   }
   return enhancedClass;
}

Look at the createClass() method of this class:

private Class<?> createClass(Enhancer enhancer) {
   Class<?> subclass = enhancer.createClass();
   // Registering callbacks statically (as opposed to thread-local)
   // is critical for usage in an OSGi environment (SPR-5932)...
   Enhancer.registerStaticCallbacks(subclass, CALLBACKS);
   return subclass;
}

Callback using dynamic proxy:

// The callbacks to use. Note that these callbacks must be stateless.
private static final Callback[] CALLBACKS = new Callback[] {
      new BeanMethodInterceptor(),
      new BeanFactoryAwareMethodInterceptor(),
      NoOp.INSTANCE
}

BeanMethodInterceptor is an internal class of ConfigurationClassEnhancer. It will execute the intercept() method of BeanMethodInterceptor interceptor class:

private static class BeanMethodInterceptor implements MethodInterceptor, ConditionalCallback {

   /**
    * Enhance a {@link Bean @Bean} method to check the supplied BeanFactory for the
    * existence of this bean object.
    * @throws Throwable as a catch-all for any exception that may be thrown when invoking the
    * super implementation of the proxied method i.e., the actual {@code @Bean} method
    */
   @Override
   @Nullable
   public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object[] beanMethodArgs,
            MethodProxy cglibMethodProxy) throws Throwable {

      ConfigurableBeanFactory beanFactory = getBeanFactory(enhancedConfigInstance);
      String beanName = BeanAnnotationHelper.determineBeanNameFor(beanMethod);

      // Determine whether this bean is a scoped-proxy
      if (BeanAnnotationHelper.isScopedProxy(beanMethod)) {
         String scopedBeanName = ScopedProxyCreator.getTargetBeanName(beanName);
         if (beanFactory.isCurrentlyInCreation(scopedBeanName)) {
            beanName = scopedBeanName;
         }
      }

      // To handle the case of an inter-bean method reference, we must explicitly check the
      // container for already cached instances.

      // First, check to see if the requested bean is a FactoryBean. If so, create a subclass
      // proxy that intercepts calls to getObject() and returns any cached bean instance.
      // This ensures that the semantics of calling a FactoryBean from within @Bean methods
      // is the same as that of referring to a FactoryBean within XML. See SPR-6602.
      if (factoryContainsBean(beanFactory, BeanFactory.FACTORY_BEAN_PREFIX + beanName) &&
            factoryContainsBean(beanFactory, beanName)) {
         Object factoryBean = beanFactory.getBean(BeanFactory.FACTORY_BEAN_PREFIX + beanName);
         if (factoryBean instanceof ScopedProxyFactoryBean) {
            // Scoped proxy factory beans are a special case and should not be further proxied
         }
         else {
            // It is a candidate FactoryBean - go ahead with enhancement
            return enhanceFactoryBean(factoryBean, beanMethod.getReturnType(), beanFactory, beanName);
         }
      }

      // If the method executed by the proxy object is the factory method of the Bean being created, execute the corresponding method to obtain the object as the Bean
      if (isCurrentlyInvokedFactoryMethod(beanMethod)) {
         // The factory is calling the bean method in order to instantiate and register the bean
         // (i.e. via a getBean() call) -> invoke the super implementation of the method to actually
         // create the bean instance.
         if (logger.isInfoEnabled() &&
               BeanFactoryPostProcessor.class.isAssignableFrom(beanMethod.getReturnType())) {
            logger.info(String.format("@Bean method %s.%s is non-static and returns an object " +
                        "assignable to Spring's BeanFactoryPostProcessor interface. This will " +
                        "result in a failure to process annotations such as @Autowired, " +
                        "@Resource and @PostConstruct within the method's declaring " +
                        "@Configuration class. Add the 'static' modifier to this method to avoid " +
                        "these container lifecycle issues; see @Bean javadoc for complete details.",
                  beanMethod.getDeclaringClass().getSimpleName(), beanMethod.getName()));
         }
         // Note that the proxy object passed in here is equivalent to the method of executing the target object
         return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);
      }
      // If the method executed by the proxy object is not the method that is creating the Bean, get it directly from the Spring container according to the name of the method (getBean() method)
      return resolveBeanReference(beanMethod, beanMethodArgs, beanFactory, beanName);
   }

Note: if there is a method with @ Bean annotation in the Configuration class with @ Configuration annotation, the method will be executed through the proxy object when instantiating and executing the invoke() method, for example:

@Configuration
public class ConverterConfig {
    
    @Bean
    public UserService userService(){
        kieasar();
        return new UserService();
    }

    @Bean
    public Kieasar kieasar() {
        return new Kieasar();
    }
}

In the above example, the proxy class of ConverterConfig will first judge whether it is the method to create a Bean when executing the userService() method. If so, create an instance of UserService, which is equivalent to the method to execute the target object, and enter cglibmethodproxy Invokesuper(), call the method of the proxied object.

Then, when the proxy object executes the kieasar() method in userService(), although the method is annotated with @ Bean, what is being executed is the userService() method that creates the userService instance, so the iscurrylinvokedfactorymethod () result is false, and the last line of resolveBeanReference() will be executed, and the getBean() method will be executed, Get the instance of the kieasar object in the container according to the name of the method.  

However, when Spring creates a Kieasar Bean, if the currently executed method happens to be the kieasar() method (that is, the method of creating a Kieasar instance), the iscurrentyinvokedfactorymethod () result is true and cglibmethodproxy is executed Invokesuper(), the method of the proxied object.

Parsing flow chart of ConfigurationClassPostProcessor class:

Finally, summarize the parsing process of ConfigurationClassPostProcessor class:

1. When starting Spring, you need to pass in a ScanBean Class to ApplicationContext, which will be encapsulated into a BeanDefinition according to the ScanBean class. We call it the configuration class BeanDefinition.

2. The configurationclasspostprocessor will take out the configuration class BeanDefinition (ScanBean).

3. Construct a ConfigurationClassParser to resolve the configuration class BeanDefinition, and generate a configuration class object ConfigurationClass.

4. If the @ Component annotation exists on the configuration class, resolve the internal class in the configuration class (there is recursion here, if the internal class is also a configuration class).

5. If the @ PropertySource annotation exists on the configuration class, resolve the annotation, get the PropertySource object, and add it to the environment.

6. If the @ ComponentScan annotation exists on the configuration class, parse the annotation and scan it to get a series of BeanDefinition objects, Then judge whether these beandefinitions are also configuration classes. BeanDefinition (as long as the @ Component annotation exists, it is a configuration class, so basically all scanned out are configuration classes). If so, continue to parse the configuration class (there is also recursion), and the corresponding ConfigurationClass will be generated.

7. If the @ Import annotation exists on the configuration class, judge the type of the imported class:

  • If it is an ImportSelector, call and execute the selectImports method to get the class name, and then parse the class as a configuration class (also recursive);
  • If it is ImportBeanDefinitionRegistrar, an ImportBeanDefinitionRegistrar instance object is generated and added to the importBeanDefinitionRegistrars property in the configuration class object (ConfigurationClass);

8. If the @ ImportResource annotation exists on the configuration class, store the imported resource path in the importedResources attribute in the configuration class object.

9. If there are @ Bean methods in the configuration class, these methods will be encapsulated as BeanMethod objects and added to the beanMethods attribute in the configuration class object.

10. If the configuration class implements some interfaces, check whether the @ Bean annotation method is defined in these interfaces.

11. If the configuration class has a parent class, resolve the parent class as a configuration class.

12. The scanbean configuration class will correspond to a ConfigurationClass. At the same time, some other configurationclasses will be generated during the parsing process. Next, use the reader to further parse the ConfigurationClass:

  • If ConfigurationClass is imported through @ Import annotation, generate a BeanDefinition for this class, parse @ Scope, @ Lazy and other annotation information on this class, and register BeanDefinition;
  • If there are some beanmethods in the ConfigurationClass, that is, some @ beans are defined, resolve these @ beans, generate the corresponding BeanDefinition, and register;
  • If some resource files are imported into ConfigurationClass, such as XX XML, then parse these XX XML file, get and register BeanDefinition;
  • If some importbeandefinitionregistrars are imported into the ConfigurationClass, execute the corresponding registerBeanDefinitions to register the BeanDefinition.

Keywords: Java Spring Back-end source code

Added by Person on Tue, 28 Dec 2021 04:08:31 +0200