Spring Boot refresh() Function: invokeBeanFactoryPostProcessors in Action

Spring Boot has largely replaced the traditional XML-based Spring configuration and has become the dominant framework in many modern software and Internet companies.

Now we begin with the refresh() method of the AbstractApplicationContext class, trace the execution of the invokeBeanFactoryPostProcessors() method and explore how Spring Boot registers BeanDefinition structures.

Entry Point: AbstractApplicationContext.refresh() in Spring Boot

This method initializes post-processing of the BeanFactory after it has been created, but before any beans are instantiated. One of its key roles is to register all BeanDefinition instances based on annotated classes like those with @Configuration, @Component, etc.

invokeBeanFactoryPostProcessors(beanFactory);

Delegation to PostProcessorRegistrationDelegate in refresh()

PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());

This static method first invokes all implementations of BeanDefinitionRegistryPostProcessor, which are used to register BeanDefinition objects, such as ConfigurationClassPostProcessor for processing @Configuration annotated classes.

Code Fragment:

List<BeanDefinitionRegistryPostProcessor> priorityOrderedPostProcessors = new ArrayList<>();
for (String ppName : postProcessorNames) {
    if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
        priorityOrderedPostProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
        processedBeans.add(ppName);
    }
}
sortPostProcessors(beanFactory, priorityOrderedPostProcessors);
registryPostProcessors.addAll(priorityOrderedPostProcessors);
invokeBeanDefinitionRegistryPostProcessors(priorityOrderedPostProcessors, registry);

Step Into: invokeBeanDefinitionRegistryPostProcessors() Flow

Among the post-processors, ConfigurationClassPostProcessor is a key player that processes classes annotated with @Configuration.

private static void invokeBeanDefinitionRegistryPostProcessors(
    Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors, 
    BeanDefinitionRegistry registry) {

    for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {
        postProcessor.postProcessBeanDefinitionRegistry(registry);
    }
}

Key Logic in postProcessBeanDefinitionRegistry() for BeanDefinition

This method is called to register BeanDefinitions based on annotated configuration classes.

This ensures that each BeanDefinitionRegistry is only processed once and safely initializes all configuration-related bean definitions.

@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    int registryId = System.identityHashCode(registry);
    if (this.registriesPostProcessed.contains(registryId)) {
        throw new IllegalStateException(...);
    }

    this.registriesPostProcessed.add(registryId);
    processConfigBeanDefinitions(registry);
}

Parsing @Configuration Classes in Spring Boot refresh()

ConfigurationClassParser parser = new ConfigurationClassParser(...);
parser.parse(candidates);
parser.validate();

Recursive Analysis with processConfigurationClass()

Detailed Parsing with doProcessConfigurationClass() and Component Scanning

This method processes @ComponentScan annotations inside a @Configuration class.

This enables Spring to discover and process nested or indirectly referenced configuration classes automatically.

protected final SourceClass doProcessConfigurationClass(...) {
    Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(...);
    for (AnnotationAttributes componentScan : componentScans) {
        Set<BeanDefinitionHolder> scannedBeanDefinitions =
            this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());

        for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
            if (ConfigurationClassUtils.checkConfigurationClassCandidate(...)) {
                parse(holder.getBeanDefinition().getBeanClassName(), holder.getBeanName());
            }
        }
    }
}

Deep Dive: ComponentScanAnnotationParser.parse() in Spring Boot

Scanning Components with ClassPathBeanDefinitionScanner

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
    for (String basePackage : basePackages) {
        Set<BeanDefinition> candidates = findCandidateComponents(basePackage);

        for (BeanDefinition candidate : candidates) {
            // Assign scope, name, and proxy metadata
            // Register into BeanFactory
            registerBeanDefinition(definitionHolder, this.registry);
        }
    }
    return beanDefinitions;
}

All valid component classes in the specified packages are converted to BeanDefinition objects and registered, so Spring can manage and inject them as needed.

Spring Boot 3.x Enhancements

  1. Kotlin and Functional Bean Registration (Optional):

In addition to @Configuration, Spring Boot 3.x also supports Kotlin DSLs and programmatic registration via GenericApplicationContext.registerBean(...).

  1. Record Support:

Java record classes can now be used directly as @ConfigurationProperties or @Component types, improving immutability and readability.

  1. Declarative Initialization with ApplicationContextInitializer:

Although ConfigurationClassPostProcessor is still used, more initialization is now often handled using lambda-based registration or externalized configuration loading strategies for performance and simplicity.

  1. Module Scanning and Native Configuration:

Spring Boot 3.x adds support for GraalVM and native images. To make applications start faster and use less memory, Spring now allows you to give explicit hints about which classes and methods should be kept at compile time. This is done using annotations like @NativeHint, @ImportRuntimeHints, and through the AOT (Ahead-Of-Time) compilation process. These tools help Spring avoid relying on runtime reflection, which native images don’t support well.