What actually happens during context refresh

Spring Boot · lesson 28 of 39 · 6 min read

The twelve phases between SpringApplication.run and a context that is ready to serve.

Open this lesson in the learning hub

Key points

  • Bean creation is not the first step. The container first builds bean definitions - metadata about what could be created - and only later instantiates them.
  • A BeanFactoryPostProcessor runs against those definitions, before any bean object exists. That is how @Value placeholders are resolved and how a definition can be modified or removed.
  • A BeanPostProcessor runs against each finished bean instance, twice: before and after initialisation callbacks. AOP proxies are created in the second hook.
  • This ordering explains a class of confusing failure: injecting a normal bean into a BeanFactoryPostProcessor forces that bean to be created far too early, before post-processors that would have configured it have run.
  • Singletons are instantiated eagerly at the end of refresh, which is why a wiring mistake fails at startup rather than on the first request. That is a feature.
  • Only after all of that does finishRefresh publish ContextRefreshedEvent and start SmartLifecycle beans - the web server among them.

Example

// Runs BEFORE any bean is instantiated - it edits definitions, not objects.
@Component
class AuditDefaultsPostProcessor implements BeanFactoryPostProcessor {

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory factory) {
        for (String name : factory.getBeanDefinitionNames()) {
            BeanDefinition def = factory.getBeanDefinition(name);
            // Safe: we are reading metadata, not calling getBean().
            if (def.getBeanClassName() != null && def.getBeanClassName().endsWith("Repository")) {
                def.setLazyInit(true);
            }
        }
    }
}

// Runs against each finished bean. The SECOND callback is where proxies appear,
// so a bean injected here may not yet be the proxy other beans will receive.
@Component
class TimingBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String name) {
        return bean;                       // before @PostConstruct / afterPropertiesSet
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String name) {
        return bean;                       // after init - AOP wraps the bean at this point
    }
}

// Ordered startup work. Phase decides who starts first and who stops last.
@Component
class WarmupLifecycle implements SmartLifecycle {

    private volatile boolean running;

    @Override public int getPhase() { return Integer.MIN_VALUE + 10; }  // very early
    @Override public void start() { running = true; }
    @Override public void stop()  { running = false; }
    @Override public boolean isRunning() { return running; }
}

Definitions are built first, then post-processed, then instantiated, then proxied - and every ordering bug you meet is a consequence of that sequence.

This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the Spring Boot course, and every lesson in it is listed on the Spring Boot contents page.