preface

In the previous article mentioned ConfigurationClassPostProcessor, see below ConfigurationClassPostProcessor acted as what kind of role in the spring.

The body of the

Source code version:

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.28..RELEASE</version>
    </dependency>
Copy the code

annotation

The first thing to look at is the class comment:

    / * * * {@link BeanFactoryPostProcessor} used for bootstrapping processing of
     * {@link Configuration @Configuration} classes. * are used to bootstrap classes that handle Configuration types (that is, classes that handle Configuration, such as by@Configuration* * <p>Registered by default when using {@code <context:annotation-config/>} or
     * {@code<context:component-scan/>}. Otherwise, may be declared manually as * with any other BeanFactoryPostProcessor. * Using <context:annotation-config/> or <context:component-scan/> is registered (to the Spring container) by default, and vice versa, It can also be declared manually like any other BeanFactoryPostProcessor. * * <p>This post processor is priority-ordered as it is important that any * {@link Bean} methods declared in {@code @Configuration} classes have
     * their corresponding bean definitions registered before any other
     * {@linkBeanFactoryPostProcessor} executes. * This post-processing is prioritized and, importantly, executes in@ConfigurationSome Bean methods declared in the labeled class (configuration class), * the Bean information corresponding to these Bean methods is registered before some other BeanFactoryPostProcessor executes. (for example, an implementation class for customizing BeanFactoryPostProcessor) */
Copy the code

ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry

Following is a method of BeanDefinitionRegistryPostProcessor interface, and see this class implements this interface to do something.

    /** * Derive further bean definitions from the configuration classes in the registry. */
   @Override
   public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
      int registryId = System.identityHashCode(registry); // Consistency hash code
      if (this.registriesPostProcessed.contains(registryId)) { // Check whether processed exists at registriesPostProcessed
         throw new IllegalStateException(
               "postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
      }
      if (this.factoriesPostProcessed.contains(registryId)) {// Check whether processed in factoriesPostProcessed exists
         throw new IllegalStateException(
               "postProcessBeanFactory already called on this post-processor against " + registry);
      }
      this.registriesPostProcessed.add(registryId);// Add to registriesPostProcessed
      processConfigBeanDefinitions(registry); 
   }
   
Copy the code

The code for this method is relatively simple, and the comments are written in the code. The main idea is to check whether the consistency ID of Registry is already in PostProcessed.

ConfigurationClassPostProcessor.processConfigBeanDefinitions

PostProcessBeanDefinitionRegistry method call processConfigBeanDefinitions method, at the end, the following is processConfigBeanDefinitions method:


    /**
    * Build and validate a configuration model based on the registry of
    * {@link Configuration} classes.
    */
   public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
       // Define a collection to collect configuration information
      List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
      // Get the names of all registered beans
      String[] candidateNames = registry.getBeanDefinitionNames();
            
        // Iterate through the collection to filter out the bean information belonging to the Configuration
      for (String beanName : candidateNames) {
         BeanDefinition beanDef = registry.getBeanDefinition(beanName);
         if(beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) ! =null) {
            if (logger.isDebugEnabled()) {
               logger.debug("Bean definition has already been processed as a configuration class: "+ beanDef); }}else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
            configCandidates.add(newBeanDefinitionHolder(beanDef, beanName)); }}// Return immediately if no @Configuration classes were found
      // Check whether there is configuration information, no return
      if (configCandidates.isEmpty()) {
         return;
      }

      // Sort by previously determined @Order value, if applicable
      // Sort the configuration information set in Order
      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; }}}// Set the standardized environment
      if (this.environment == null) {
         this.environment = new StandardEnvironment();
      }

      // Parse each @configuration class // Build a parser for the Configuration file
      ConfigurationClassParser parser = new ConfigurationClassParser(
            this.metadataReaderFactory, this.problemReporter, this.environment,
            this.resourceLoader, this.componentScanBeanNameGenerator, registry);
        // Put the configuration information into the set.
      Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
      // Already converted
      Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size()); 
      do {
          // Parse the configuration file
         parser.parse(candidates);
         parser.validate(); / / verification
            // Parsed configuration file
         Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
         configClasses.removeAll(alreadyParsed);// Remove parsed files

         // 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); // Read the configuration file
         alreadyParsed.addAll(configClasses); // Add to the collection of parsed files

         candidates.clear(); // Clear the cache
         // Determine if the number of registered beans after parsing is greater than the number before parsing (which is almost always the case)
         if (registry.getBeanDefinitionCount() > candidateNames.length) { 
             // The following is to determine whether there is any missing configuration information in the preceding parsing process
            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);if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) && ! alreadyParsedClasses.contains(bd.getBeanClassName())) { candidates.add(newBeanDefinitionHolder(bd, candidateName)); } } } candidateNames = newCandidateNames; }}while(! candidates.isEmpty());// Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes
      // Register ImportRegistry as a bean to support the ImportAware configuration class
      if(sbr ! =null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
         sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
      }
        // Clear the cache
      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(); }}Copy the code

The above code is also relatively simple, the comments are basically noted inside the code. The most important of these is the ConfigurationClassParser class, which by its name parses configuration classes. Let’s see how he parses the configuration class.

ConfigurationClassParser. Parse method

Begin to enter into the org. Springframework. Context. The annotation. ConfigurationClassParser# parse (),

The code for this method is also relatively simple: determine the type of the Config class and choose how to parse it. Finally they are unified call to processConfigurationClass this method


    public void parse(Set<BeanDefinitionHolder> configCandidates) {
      for (BeanDefinitionHolder holder : configCandidates) {
         BeanDefinition bd = holder.getBeanDefinition();
         try {
            if (bd instanceof AnnotatedBeanDefinition) {
               parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
            }
            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); }}this.deferredImportSelectorHandler.process();
   }
Copy the code

ConfigurationClassParser.processConfigurationClass

    protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {
        // This place handles @conditional annotations,
      if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
         return;
      }
      ConfigurationClass existingClass = this.configurationClasses.get(configClass);
      if(existingClass ! =null) {
         if (configClass.isImported()) {
            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.
      SourceClass sourceClass = asSourceClass(configClass, filter);
      do {
         sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
      }
      while(sourceClass ! =null);
      this.configurationClasses.put(configClass, configClass);
   }
Copy the code

This method code is relatively simple, and I have no comment, there are two points, one is conditionEvaluator shouldSkip, he is mainly dealing with @ Conditional comments, here is not to look at the specific implementation. Another is doProcessConfigurationClass method, this method directly on locating the doProcessConfigurationClass method.

ConfigurationClassParser.doProcessConfigurationClass

This method is really the heart of parsing configuration files.

    protected final SourceClass doProcessConfigurationClass( ConfigurationClass configClass, SourceClass sourceClass, Predicate
       
         filter)
       
         throws IOException {

        Members of the class / / recursive processing, if there are members of the class recursive call processConfigurationClass processing
      if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
         // Recursively process any member (nested) classes first
         processMemberClasses(configClass, sourceClass, filter);
      }

      Annotations // Process any @propertysource annotations // Process @propertysource annotations
      for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
            sourceClass.getMetadata(), PropertySources.class,
            org.springframework.context.annotation.PropertySource.class)) {
         if (this.environment instanceof ConfigurableEnvironment) {
            processPropertySource(propertySource);
         }
         else {
            logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
                  "]. Reason: Environment must implement ConfigurableEnvironment"); }}// Process any @ComponentScan annotations
      // Handle ComponentScans and ComponentScan annotations
      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
            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();
               }
               if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) { parse(bdCand.getBeanClassName(), holder.getBeanName()); }}}}// Process any @Import annotations
      // Handle the @import annotation
      processImports(configClass, sourceClass, getImports(sourceClass), filter, true);

      // Process any @ImportResource annotations
      / / @ ImportResource processing
      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); configClass.addImportedResource(resolvedResource, readerClass); }}// Process individual @Bean methods
      // Process a single bean method
      Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
      for (MethodMetadata methodMetadata : beanMethods) {
         configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
      }

      // Process default methods on interfaces
      // Handle interface default methods
      processInterfaces(configClass, sourceClass);

      // Process superclass, if any
      // Superclass processing, 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
            returnsourceClass.getSuperClass(); }}// No superclass -> processing is complete
      return null;
   }
Copy the code

Here is a flowchart for parsing configuration files:

ConfigurationClassPostProcessor.postProcessBeanFactory

ConfigurationClassPostProcessor realized BeanDefinitionRegistryPostProcessor, another realization method is postProcessBeanFactory.

    @Override
   public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
       // Consistency hashcode
      int factoryId = System.identityHashCode(beanFactory);
      if (this.factoriesPostProcessed.contains(factoryId)) {// Check if it has already been called
         throw new IllegalStateException(
               "postProcessBeanFactory already called on this post-processor against " + beanFactory);
      }
      this.factoriesPostProcessed.add(factoryId);// Add it to the factory
      if (!this.registriesPostProcessed.contains(factoryId)) {
          / / BeanDefinitionRegistryPostProcessor not be invoked when the call can be a lazy loading
         // BeanDefinitionRegistryPostProcessor hook apparently not supported...
         // Simply call processConfigurationClasses lazily at this point then.
         processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);
      }
        // Enhance the configuration class
      enhanceConfigurationClasses(beanFactory);
      / / register ImportAwareBeanPostProcessor
      beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
   }
Copy the code

This method is the most important is the last line, registered ImportAwareBeanPostProcessor, then this ImportAwareBeanPostProcessor is stem what of, you only post his two implementation methods of code:

        @Override
      public PropertyValues postProcessProperties(@Nullable PropertyValues pvs, Object bean, String beanName) {
         // Inject the BeanFactory before AutowiredAnnotationBeanPostProcessor's
         // postProcessProperties method attempts to autowire other configuration beans.
         if (bean instanceof EnhancedConfiguration) {
            ((EnhancedConfiguration) bean).setBeanFactory(this.beanFactory);
         }
         return pvs;
      }

      @Override
      public Object postProcessBeforeInitialization(Object bean, String beanName) {
         if (bean instanceof ImportAware) {
            ImportRegistry ir = this.beanFactory.getBean(IMPORT_REGISTRY_BEAN_NAME, ImportRegistry.class);
            AnnotationMetadata importingClass = ir.getImportingClassFor(ClassUtils.getUserClass(bean).getName());
            if(importingClass ! =null) { ((ImportAware) bean).setImportMetadata(importingClass); }}return bean;
      }
Copy the code

The postProcessProperties method handles the implementation class of the EnhancedConfiguration interface. PostProcessBeforeInitialization is processing ImportAware interface implementation class.

The last

To achieve a ConfigurationClassPostProcessor BeanDefinitionRegistryPostProcessor, PostProcessBeanDefinitionRegistry method is mainly used for processing parsing configuration information, and mainly is the registered ImportAwareBeanPostProcessor postProcessBeanFactory method.

Actually in springboot source, there are a lot of configuration, the configuration class is ConfigurationClassPostProcessor parsing.