namalzi

← All posts

Digging into Spring's Source 7: The Twelve Steps of refresh() — the Order in Which the Container Starts

Spring · Published

Behind the single line new AnnotationConfigApplicationContext(...) runs refresh(). This post opens its twelve steps one by one, and checks in the real source and with runnable examples what ApplicationContext adds on top of BeanFactory, why there are two branches (GenericApplicationContext and AbstractRefreshableApplicationContext), and what happens when startup fails or when close() is called.

What this post covers

This is the seventh post in the "Digging into Spring's Source" series. Part 6 looked at the places where you can hook into the container while a single bean is being built (BeanPostProcessor, lifecycle callbacks such as @PostConstruct) and the order in which they are called. This time we zoom out to the order in which the container itself starts up. Part 1 and Part 2 said "refresh() walks through twelve steps" and moved on; here we open that method line by line.

There are four questions to answer.

Item Details
Source Spring Framework 7.1.0-SNAPSHOT
Commit f447f3c310 (2026-10-01, main branch)
Module read most spring-context
Classes read most AbstractApplicationContext, GenericApplicationContext, AbstractRefreshableApplicationContext, ConfigurableApplicationContext

Every line number and every detail in this post refers to that commit. In other versions the line numbers will differ and some details may too. Part 1 explains how to get the source at the same commit.

My study notes go much deeper into each step. This post keeps only the main thread that someone seeing Spring for the first time can follow. What was left out is named at the end, under "If you want to go deeper".

Words to know first

Bean and container mean the same as in the earlier posts. A bean is an object Spring creates and looks after for you; the container is what builds and keeps those beans. The container's core interface is BeanFactory (Part 3), and how a single bean gets built is in Part 4.

ApplicationContext is an interface that extends BeanFactory. It is what you create with new AnnotationConfigApplicationContext(...). Besides handing out beans it also reads configuration properties, publishes events and loads files. Section 1 shows exactly what it adds. In this post, "the context" means an ApplicationContext.

refresh() is the method that actually starts the context. Reading the blueprints, processing them, creating the beans and announcing "ready" all happen inside it. Why it is called "refresh" is explained in section 7.

The template method pattern is a design where a parent class fixes the whole sequence in one method and lets subclasses fill in only a few of the steps. refresh() is exactly that "method that fixes the sequence", and there are a few slots for subclasses to fill.

Events and listeners: the context turns things like "I have started" and "I am closing" into event objects and publishes them, and beans that implement ApplicationListener receive them. Only two events appear in this post, ContextRefreshedEvent and ContextClosedEvent. The event mechanism itself is the subject of Part 12.

Post-processors were the subject of Part 6. A BeanFactoryPostProcessor edits blueprints (BeanDefinitions); a BeanPostProcessor edits bean objects while they are being built. Here we only look at which step of refresh() calls each of them.

1. What ApplicationContext adds to BeanFactory

The answer is in one line of the interface declaration.

public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory,
		MessageSource, ApplicationEventPublisher, ResourcePatternResolver {

spring-context/src/main/java/org/springframework/context/ApplicationContext.java lines 59–60

It bundles six interfaces. Two are from the BeanFactory family we saw in Part 3; four are new.

Interface What it adds Where it comes from
ListableBeanFactory Look beans up as a list, not just one name at a time spring-beans (Part 3)
HierarchicalBeanFactory Can have a parent container spring-beans (Part 3)
EnvironmentCapable Has properties and profiles (Environment) spring-core
MessageSource Resolves internationalised messages spring-context
ApplicationEventPublisher Publishes events (publishEvent) spring-context
ResourcePatternResolver Loads files and classpath resources by pattern spring-core

The six interfaces ApplicationContext extends, ConfigurableApplicationContext adding Lifecycle and Closeable on top, and AbstractApplicationContext implementing it. The two grey boxes are the BeanFactory family from Part 3. The two blue ones are the stage for this post.

Notice that neither refresh() nor close() is in that list. As its Javadoc says, ApplicationContext is read-only (lines 28–30); the methods that start and stop it live one level down, in ConfigurableApplicationContext.

public interface ConfigurableApplicationContext extends ApplicationContext, Lifecycle, Closeable {

spring-context/src/main/java/org/springframework/context/ConfigurableApplicationContext.java line 45

This is where refresh() (line 222), close() (line 267), registerShutdownHook() (line 256), isActive() (line 289) and getBeanFactory(), which exposes the BeanFactory inside (line 310), are declared. Code that merely uses beans gets the read-only ApplicationContext; starting and stopping is opened up only through Configurable….

The class that actually implements all of this is AbstractApplicationContext (AbstractApplicationContext lines 145–146). Almost every method read in this post is in that class.

The context has a BeanFactory

Look at how AbstractApplicationContext implements a BeanFactory method and the design shows itself.

	@Override
	public Object getBean(String name) throws BeansException {
		assertBeanFactoryActive();
		return getBeanFactory().getBean(name);
	}

spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java lines 1297–1301

It does not build anything itself; it delegates to the BeanFactory it holds inside. The context did not grow by inheriting from BeanFactory. It holds one DefaultListableBeanFactory (from Part 4) and wraps properties, events and messages around it. The assertBeanFactoryActive() in front is a one-line "are we switched on?" check.

	protected void assertBeanFactoryActive() {
		if (!this.active.get()) {
			if (this.closed.get()) {
				throw new IllegalStateException(getDisplayName() + " has been closed already");
			}
			else {
				throw new IllegalStateException(getDisplayName() + " has not been refreshed yet");
			}
		}
	}

spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java lines 1281–1290

active and closed are two AtomicBoolean fields (lines 209–212). refresh() sets active; close() sets closed and clears active. These two flags run through the whole post.

2. The skeleton of refresh() — counted: twelve

Part 1 showed that the constructor of new AnnotationConfigApplicationContext(AppConfig.class) is two lines, register and refresh (AnnotationConfigApplicationContext lines 89–93). Here is the body of that refresh(). The exception handling is covered separately in section 8, so it is shortened to // ... here.

	@Override
	public void refresh() throws BeansException, IllegalStateException {
		this.startupShutdownLock.lock();
		try {
			this.startupShutdownThread = Thread.currentThread();

			StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");

			// Prepare this context for refreshing.
			prepareRefresh();

			// Tell the subclass to refresh the internal bean factory.
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			// Prepare the bean factory for use in this context.
			prepareBeanFactory(beanFactory);

			try {
				// Allows post-processing of the bean factory in context subclasses.
				postProcessBeanFactory(beanFactory);

				StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
				// Invoke factory processors registered as beans in the context.
				invokeBeanFactoryPostProcessors(beanFactory);
				// Register bean processors that intercept bean creation.
				registerBeanPostProcessors(beanFactory);
				beanPostProcess.end();

				// Initialize message source for this context.
				initMessageSource();

				// Initialize event multicaster for this context.
				initApplicationEventMulticaster();

				// Initialize other special beans in specific context subclasses.
				onRefresh();

				// Check for listener beans and register them.
				registerListeners();

				// Instantiate all remaining (non-lazy-init) singletons.
				finishBeanFactoryInitialization(beanFactory);

				// Last step: publish corresponding event.
				finishRefresh();
			}

			catch (RuntimeException | Error ex) {
				// ...
			}

			finally {
				contextRefresh.end();
			}
		}
		finally {
			this.startupShutdownThread = null;
			this.startupShutdownLock.unlock();
		}
	}

spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java lines 581–662

Count the method calls that each carry a comment, from prepareRefresh to finishRefresh, and there are exactly twelve. The "twelve steps" that Parts 1 and 2 mentioned holds for this commit. StartupStep is a timing tool that measures each section, so it is not counted as a step.

The twelve steps of refresh() grouped into four blocks: prepare, process the blueprints, set up the extras, create beans and start running. The four blocks from Part 2 lined up with the twelve steps. Grey boxes are slots for subclasses whose default implementation is empty, blue ones are post-processors and bean creation, and green is the last step.

Step Method In one line Section
1 prepareRefresh Set active, check the environment, prepare the bucket for early events 3
2 obtainFreshBeanFactory Get the BeanFactory to hold inside (where the two branches split) 3, 7
3 prepareBeanFactory Plug context-level machinery into the BeanFactory 3
4 postProcessBeanFactory Slot for subclasses (empty by default) 4
5 invokeBeanFactoryPostProcessors Edit and add blueprints 4
6 registerBeanPostProcessors Create and register the bean post-processors first 4
7 initMessageSource Set up the message source 5
8 initApplicationEventMulticaster Set up the event multicaster 5
9 onRefresh Slot for subclasses (empty by default) 5
10 registerListeners Register listeners, deliver events held back so far 5
11 finishBeanFactoryInitialization Create every singleton bean 6
12 finishRefresh Start Lifecycle beans, publish ContextRefreshedEvent 6

The startupShutdownLock at the top is a lock that stops starting and stopping from happening at the same time (line 215). close() in section 8 takes the same lock. startupShutdownThread records which thread currently holds it (line 218).

3. Steps 1 to 3: prepare

Step 1, prepareRefresh() (lines 668–703) does four things. It records the start time, clears closed and sets active (lines 670–672). It checks that every property the Environment marks as required is present (line 688). It keeps a separate copy of listeners registered in code before refresh() (lines 691–698). And it creates an empty set called earlyApplicationEvents (line 702). What that set is for becomes clear in section 5. (The call to initPropertySources(), whose default implementation is empty (lines 710–712), is not counted.)

Step 2, obtainFreshBeanFactory(), is two lines.

	protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
		refreshBeanFactory();
		return getBeanFactory();
	}

spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java lines 720–723

Both refreshBeanFactory() and getBeanFactory() are abstract methods (line 1633, line 1656). How the inner BeanFactory is obtained is left to the subclass. That is the fork of section 7.

Step 3, prepareBeanFactory(), is the preparation step most worth reading. It takes the plain BeanFactory just obtained and plugs in what it needs to work as "the BeanFactory inside a context". Keeping only the essentials:

	protected void prepareBeanFactory(ConfigurableListableBeanFactory beanFactory) {
		// Tell the internal bean factory to use the context's class loader etc.
		beanFactory.setBeanClassLoader(getClassLoader());
		// ...

		// Configure the bean factory with context callbacks.
		beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this));
		beanFactory.ignoreDependencyInterface(EnvironmentAware.class);
		// ...
		beanFactory.ignoreDependencyInterface(ApplicationContextAware.class);
		// ...

		// BeanFactory interface not registered as resolvable type in a plain factory.
		// MessageSource registered (and found for autowiring) as a bean.
		beanFactory.registerResolvableDependency(BeanFactory.class, beanFactory);
		beanFactory.registerResolvableDependency(ResourceLoader.class, this);
		beanFactory.registerResolvableDependency(ApplicationEventPublisher.class, this);
		beanFactory.registerResolvableDependency(ApplicationContext.class, this);

		// Register early post-processor for detecting inner beans as ApplicationListeners.
		beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(this));
		// ...

		// Register default environment beans.
		if (!beanFactory.containsLocalBean(ENVIRONMENT_BEAN_NAME)) {
			beanFactory.registerSingleton(ENVIRONMENT_BEAN_NAME, getEnvironment());
		}
		// ...
	}

spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java lines 730–776

Three things get plugged in.

  1. ApplicationContextAwareProcessor (line 737). A BeanPostProcessor that hands the context or the Environment to any bean implementing ApplicationContextAware, EnvironmentAware and so on. Open it and postProcessBeforeInitialization checks seven interfaces in turn with instanceof (ApplicationContextAwareProcessor lines 83–112). So "the context recognises Aware interfaces" is implemented with one of the post-processors we met in Part 4. The ignoreDependencyInterface calls just below (lines 738–744) exclude the setters of those same seven interfaces from autowiring: the post-processor will fill them, so injection should keep out.
  2. registerResolvableDependency (lines 748–751). These four lines are why @Autowired ApplicationContext ctx; works even though no bean of type BeanFactory or ApplicationContext was ever registered. They tell the BeanFactory in advance: "if someone asks for this type, give them this object." As the comment says, MessageSource is not here; it is registered as a bean in step 7.
  3. ApplicationListenerDetector (line 754). A post-processor that puts a freshly built bean on the listener list if it is an ApplicationListener (ApplicationListenerDetector lines 75–95). It is a safety net for inner beans that registerListeners in step 10 cannot find by name.

Finally it registers four objects as singleton beans: environment, systemProperties, systemEnvironment and applicationStartup (lines 764–775). That is why @Autowired Environment env; works.

The four kinds of things step 3, prepareBeanFactory(), plugs into the BeanFactory obtained in step 2. The post-processor list gets, ahead of step 6, ① ApplicationContextAwareProcessor, which fills in the seven Aware kinds, and ② ApplicationListenerDetector, which puts listener beans on the list. ignoreDependencyInterface excludes the setters of the same seven Aware kinds from autowiring. registerResolvableDependency records four types that can be injected without being beans (BeanFactory, ResourceLoader, ApplicationEventPublisher, ApplicationContext). And environment, systemProperties, systemEnvironment and applicationStartup are put in directly as singletons What the code above plugs in, grouped by kind. Blue is the post-processors that work while beans are created; yellow is objects handed out during injection (the LoadTimeWeaver lines trimmed from the code above are left out).

Easy to get wrong

4. Steps 4 to 6: process the blueprints

Step 4, postProcessBeanFactory(), has an empty default implementation (lines 787–788). It is one of the "slots a subclass fills" in the template method pattern. The moment described in its Javadoc matters: the blueprints have been loaded, but no post-processor has run and not a single bean has been created (lines 779–782). The web contexts plug their servlet-related post-processors in here (GenericWebApplicationContext line 153; Part 20 covers it).

Step 5, invokeBeanFactoryPostProcessors(), is a one-line delegation.

	protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
		PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
		// ...
	}

spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java lines 795–805

getBeanFactoryPostProcessors() is the list of processors added in code with addBeanFactoryPostProcessor (line 203); the BeanFactoryPostProcessors registered as beans are found in the BeanFactory, created and called in order by PostProcessorRegistrationDelegate (from PostProcessorRegistrationDelegate line 68). The ordering rules were the subject of Part 6, so they are not repeated here.

This step matters because annotation configuration is interpreted here. ConfigurationClassPostProcessor, which reads @Configuration classes and adds the @Bean methods and @ComponentScan results as blueprints, is itself a kind of BeanFactoryPostProcessor, so it runs in this step (registered at AnnotationConfigUtils lines 158–161). This is what Part 2 meant by "much of collecting definitions is implemented with the machinery for processing them". The details are in Part 8.

Step 6, registerBeanPostProcessors(), delegates the same way (lines 812–814; the implementation starts at PostProcessorRegistrationDelegate line 211). It creates every BeanPostProcessor bean now and plugs them into the BeanFactory. A post-processor has to exist before the ordinary beans it will intercept, which is why this is step 6 rather than step 11.

The difference between steps 5 and 6. Step 5, invokeBeanFactoryPostProcessors, finds and creates the BeanFactoryPostProcessor beans (plus any added in code) and calls them in order so they can edit and add definitions; for example, ConfigurationClassPostProcessor adds the @Bean and @ComponentScan results as definitions. Their work ends in this step. Step 6, registerBeanPostProcessors, creates every BeanPostProcessor bean now, before ordinary beans, and only plugs them into the BeanFactory's post-processor list; they are called only in step 11, each time a bean is created Two steps with similar names, but step 5's post-processors work on the spot while step 6's wait until step 11.

Easy to get wrong

5. Steps 7 to 10: set up the extras

Steps 7 and 8 have the same shape: "if a bean with this name exists, use it; otherwise create the default and register it as a bean."

Step 9, onRefresh(), is another empty slot like step 4 (lines 907–909). As its Javadoc says, it is where a subclass "initialises special beans, before singletons are instantiated". No class in any module of this repository overrides it (apart from one test); it is a slot for frameworks built on top. (Spring Boot is known to start its embedded web server here, but Spring Boot is a separate project from this repository, so that is not something this post verified in the source.)

Step 10, registerListeners(), has three parts.

	protected void registerListeners() {
		// Register statically specified listeners first.
		for (ApplicationListener<?> listener : getApplicationListeners()) {
			getApplicationEventMulticaster().addApplicationListener(listener);
		}

		// Do not initialize FactoryBeans here: We need to leave all regular beans
		// uninitialized to let post-processors apply to them!
		String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
		for (String listenerBeanName : listenerBeanNames) {
			getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
		}

		// Publish early application events now that we finally have a multicaster...
		Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;
		this.earlyApplicationEvents = null;
		if (!CollectionUtils.isEmpty(earlyEventsToProcess)) {
			for (ApplicationEvent earlyEvent : earlyEventsToProcess) {
				getApplicationEventMulticaster().multicastEvent(earlyEvent);
			}
		}
	}

spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java lines 915–936

The first part is for listeners registered directly in code, the second for listeners registered as beans. Note that the second part passes only names (addApplicationListenerBean). As the comment says, this is not yet the time to create beans, so it writes down the name and the actual object is fetched after step 11.

The third part is what the earlyApplicationEvents set from step 1 is for. Open publishEvent() and you see that, while that set is not null, events are not delivered but collected.

		// Multicast right now if possible - or lazily once the multicaster is initialized
		if (this.earlyApplicationEvents != null) {
			this.earlyApplicationEvents.add(applicationEvent);
		}
		else if (this.applicationEventMulticaster != null) {
			this.applicationEventMulticaster.multicastEvent(applicationEvent, eventType);
		}

spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java lines 444–450

Step 1 creates the set (so it is no longer null) and step 10 sets it back to null, so anything published in between — by a BeanFactoryPostProcessor in step 5, say — piles up in the set and goes out all at once in step 10. It is a device that stops events sent before there is a multicaster (step 8) or any listeners (step 10) from being lost.

The path of early events. Step 1, prepareRefresh, creates an empty set in earlyApplicationEvents (so it is no longer null). If publishEvent() is called between steps 1 and 10 (for example by a BeanFactoryPostProcessor in step 5), the event is not sent but stored in the set. Step 8, initApplicationEventMulticaster, creates the event multicaster. Step 10 registers ① the listeners added in code and ② the names of the listener beans with the multicaster, then ③ takes the set, sets the field back to null and multicasts the stored events all at once. After that, publishEvent() goes straight to the multicaster The "collecting time" is while earlyApplicationEvents is not null (steps 1–10).

Easy to get wrong

6. Steps 11 and 12: create beans and start running

Step 11, finishBeanFactoryInitialization(), is where the preInstantiateSingletons() from Part 4 is called. A few lines of preparation come first.

	protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
		// ...
		// Initialize conversion service for this context.
		if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) &&
				beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) {
			beanFactory.setConversionService(
					beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));
		}

		// Register a default embedded value resolver if no BeanFactoryPostProcessor
		// (such as a PropertySourcesPlaceholderConfigurer bean) registered any before:
		// at this point, primarily for resolution in annotation attribute values.
		if (!beanFactory.hasEmbeddedValueResolver()) {
			beanFactory.addEmbeddedValueResolver(strVal -> getEnvironment().resolvePlaceholders(strVal));
		}
		// ...

		// Stop using the temporary ClassLoader for type matching.
		beanFactory.setTempClassLoader(null);

		// Allow for caching all bean definition metadata, not expecting further changes.
		beanFactory.freezeConfiguration();

		// Instantiate all remaining (non-lazy-init) singletons.
		beanFactory.preInstantiateSingletons();
	}

spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java lines 943–996

Step 12, finishRefresh(), is five lines.

	protected void finishRefresh() {
		// Reset common introspection caches in Spring's core infrastructure.
		resetCommonCaches();

		// Clear context-level resource caches (such as ASM metadata from scanning).
		clearResourceCaches();

		// Initialize lifecycle processor for this context.
		initLifecycleProcessor();

		// Propagate refresh to lifecycle processor first.
		getLifecycleProcessor().onRefresh();

		// Publish the final event.
		publishEvent(new ContextRefreshedEvent(this));
	}

spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java lines 1003–1018

Example: which step calls what

One hook each in steps 5, 6, 11 and 12, and we watch the order of the output.

// An example written for this post (not Spring's code)
package ex.u05;

// ... (imports omitted)

public class RefreshOrderExample {

    // Step 5, invokeBeanFactoryPostProcessors: the moment the blueprints (BeanDefinitions) can be edited
    static class LoggingFactoryPostProcessor implements BeanFactoryPostProcessor {
        @Override
        public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
            String name = "refreshOrderExample.Greeter";   // a nested class gets a bean name like this
            System.out.println("[5] BeanFactoryPostProcessor: Greeter definition="
                    + beanFactory.containsBeanDefinition(name)
                    + ", instance=" + beanFactory.containsSingleton(name));
        }
    }

    // Registered in step 6, registerBeanPostProcessors; called for every bean in step 11
    static class LoggingPostProcessor implements BeanPostProcessor {
        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName) {
            if (bean.getClass().getPackageName().startsWith("ex.u05")) {
                System.out.println("[11]   BeanPostProcessor before: " + bean.getClass().getSimpleName());
            }
            return bean;
        }

        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) {
            if (bean.getClass().getPackageName().startsWith("ex.u05")) {
                System.out.println("[11]   BeanPostProcessor after:  " + bean.getClass().getSimpleName());
            }
            return bean;
        }
    }

    // Step 11, finishBeanFactoryInitialization: one ordinary singleton bean
    static class Greeter implements ApplicationContextAware, SmartInitializingSingleton {
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) {
            System.out.println("[11]   ApplicationContextAware: Greeter");
        }

        @PostConstruct
        void init() {
            System.out.println("[11]   @PostConstruct: Greeter");
        }

        @Override
        public void afterSingletonsInstantiated() {
            System.out.println("[11] SmartInitializingSingleton: after every singleton is created");
        }
    }

    // Step 12, finishRefresh: LifecycleProcessor.onRefresh() calls start()
    static class Starter implements SmartLifecycle {
        private boolean running;

        @Override
        public void start() {
            this.running = true;
            System.out.println("[12] SmartLifecycle.start()");
        }

        @Override
        public void stop() {
            this.running = false;
        }

        @Override
        public boolean isRunning() {
            return this.running;
        }
    }

    // Step 12, finishRefresh: ContextRefreshedEvent comes last
    static class RefreshedListener implements ApplicationListener<ContextRefreshedEvent> {
        @Override
        public void onApplicationEvent(ContextRefreshedEvent event) {
            System.out.println("[12] ContextRefreshedEvent");
        }
    }

    public static void main(String[] args) {
        System.out.println("-- refresh() begins");
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
                LoggingFactoryPostProcessor.class, LoggingPostProcessor.class,
                Greeter.class, Starter.class, RefreshedListener.class);
        System.out.println("-- refresh() done: " + context.getBean(Greeter.class).getClass().getSimpleName());
        context.close();
    }
}

The output:

-- refresh() begins
[5] BeanFactoryPostProcessor: Greeter definition=true, instance=false
[11]   ApplicationContextAware: Greeter
[11]   BeanPostProcessor before: Greeter
[11]   @PostConstruct: Greeter
[11]   BeanPostProcessor after:  Greeter
[11]   BeanPostProcessor before: Starter
[11]   BeanPostProcessor after:  Starter
[11]   BeanPostProcessor before: RefreshedListener
[11]   BeanPostProcessor after:  RefreshedListener
[11] SmartInitializingSingleton: after every singleton is created
[12] SmartLifecycle.start()
[12] ContextRefreshedEvent
-- refresh() done: Greeter

The example output grouped by step. In step 5, the BeanFactoryPostProcessor sees only Greeter's definition and no object (definition=true, instance=false). In step 11, finishBeanFactoryInitialization uses preInstantiateSingletons to create Greeter (ApplicationContextAware → before → @PostConstruct → after), Starter (before → after) and RefreshedListener (before → after) in turn, and once all three exist, SmartInitializingSingleton is called once. In step 12, finishRefresh, LifecycleProcessor.onRefresh() calls SmartLifecycle.start(), and ContextRefreshedEvent goes out last The [5], [11] and [12] lines of the output above, sorted into step boxes. The order is exactly the actual output.

Four things to read off it.

This example was run on the released Spring Framework 7.0.9 and produced the output above (it was not run on a self-built 7.1.0-SNAPSHOT, the version this series reads).

7. Two branches: GenericApplicationContext and AbstractRefreshableApplicationContext

refreshBeanFactory() from step 2 was abstract. Two subclasses fill it in, in opposite ways.

The two branches below AbstractApplicationContext. GenericApplicationContext creates its BeanFactory once in the constructor and rejects a second refresh; AbstractRefreshableApplicationContext creates a new BeanFactory on every refresh and reads the definitions again. The yellow boxes show how each branch obtains its BeanFactory; the red box is the fate of a second refresh().

GenericApplicationContext creates its BeanFactory once, in the constructor, and keeps it in a final field.

	private final DefaultListableBeanFactory beanFactory;
	// ...
	private final AtomicBoolean refreshed = new AtomicBoolean();


	/**
	 * Create a new GenericApplicationContext.
	 * @see #registerBeanDefinition
	 * @see #refresh
	 */
	public GenericApplicationContext() {
		this.beanFactory = new DefaultListableBeanFactory();
	}

spring-context/src/main/java/org/springframework/context/support/GenericApplicationContext.java lines 114–130

So all its refreshBeanFactory() has to do is "make sure this is not the second time".

	@Override
	protected final void refreshBeanFactory() throws IllegalStateException {
		if (!this.refreshed.compareAndSet(false, true)) {
			throw new IllegalStateException(
					"GenericApplicationContext does not support multiple refresh attempts: just call 'refresh' once");
		}
		this.beanFactory.setSerializationId(getId());
	}

spring-context/src/main/java/org/springframework/context/support/GenericApplicationContext.java lines 295–302

Having the BeanFactory from the start means blueprints can be registered before refresh(): registerBean(Class) (lines 499–500) or registerBeanDefinition from BeanDefinitionRegistry. The AnnotationConfigApplicationContext of Part 1 is on this branch (AnnotationConfigApplicationContext line 57); the register(componentClasses) in its constructor is exactly "register blueprints before refresh()".

AbstractRefreshableApplicationContext does the opposite: it creates a new BeanFactory on every refresh().

	@Override
	protected final void refreshBeanFactory() throws BeansException {
		if (hasBeanFactory()) {
			destroyBeans();
			closeBeanFactory();
		}
		try {
			DefaultListableBeanFactory beanFactory = createBeanFactory();
			beanFactory.setSerializationId(getId());
			beanFactory.setApplicationStartup(getApplicationStartup());
			customizeBeanFactory(beanFactory);
			loadBeanDefinitions(beanFactory);
			this.beanFactory = beanFactory;
		}
		catch (IOException ex) {
			throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
		}
	}

spring-context/src/main/java/org/springframework/context/support/AbstractRefreshableApplicationContext.java lines 118–135

If a BeanFactory already exists, it destroys the beans and drops it, creates a new DefaultListableBeanFactory (lines 195–197) and reads the blueprints again through the abstract loadBeanDefinitions(). ClassPathXmlApplicationContext, which reads XML files, is on this branch (ClassPathXmlApplicationContext line 53; its loadBeanDefinitions is at AbstractXmlApplicationContext line 83). "Edit the config file and call refresh() again to reload it" was the original use case, and it is this branch that gives the method its name, refresh. The class Javadoc describes it as the base class for contexts that "support multiple calls to refresh(), creating a new internal bean factory instance every time" (lines 29–32).

GenericApplicationContext AbstractRefreshableApplicationContext
When the BeanFactory is created Once, in the constructor On every refresh()
Who puts the blueprints in The outside (registerBean, a BeanDefinitionReader) The inside (loadBeanDefinitions)
Calling refresh() twice IllegalStateException Allowed: all beans are dropped and the definitions re-read
Typical subclass AnnotationConfigApplicationContext ClassPathXmlApplicationContext
getBeanFactory() Always returns the same object (lines 324–326) IllegalStateException if there is none (lines 164–171)

Example: before refresh(), after it, a second time, and after close()

// An example written for this post (not Spring's code)
package ex.u05;

// ... (imports omitted)

public class RefreshTwiceExample {

    static class Greeter {
        String hello() {
            return "hello";
        }
    }

    public static void main(String[] args) {
        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean(Greeter.class);           // only the blueprint — no object yet

        try {
            context.getBean(Greeter.class);            // ask for it before refresh()?
        }
        catch (IllegalStateException ex) {
            System.out.println("before refresh, getBean: " + ex.getMessage());
        }

        context.refresh();                             // start the container
        System.out.println("after refresh, getBean: " + context.getBean(Greeter.class).hello());

        try {
            context.refresh();                         // start it once more?
        }
        catch (IllegalStateException ex) {
            System.out.println("second refresh: " + ex.getMessage());
        }

        context.close();
        try {
            context.getBean(Greeter.class);            // ask for it after close()?
        }
        catch (IllegalStateException ex) {
            System.out.println("after close, getBean: " + ex.getMessage());
        }
    }
}
before refresh, getBean: org.springframework.context.support.GenericApplicationContext@6fdb1f78 has not been refreshed yet
after refresh, getBean: hello
second refresh: GenericApplicationContext does not support multiple refresh attempts: just call 'refresh' once
after close, getBean: org.springframework.context.support.GenericApplicationContext@6fdb1f78 has been closed already

The first and fourth lines come from assertBeanFactoryActive() in section 1; the third from the refreshBeanFactory() above. They are all IllegalStateException, but the message tells you which flag caused it. (The @6fdb1f78 part is an object hash and differs on every run.) This example too was run on Spring Framework 7.0.9.

Easy to get wrong

8. When it fails, and when it closes

An exception in the middle of refresh()

Here is the catch block that section 2 shortened.

			catch (RuntimeException | Error ex) {
				if (logger.isWarnEnabled()) {
					logger.warn("Exception encountered during context initialization - " +
							"cancelling refresh attempt: " + ex);
				}

				// Stop already started Lifecycle beans to avoid dangling resources.
				if (this.lifecycleProcessor != null && this.lifecycleProcessor.isRunning()) {
					try {
						this.lifecycleProcessor.stop();
					}
					catch (Throwable ex2) {
						logger.warn("Exception thrown from LifecycleProcessor on cancelled refresh", ex2);
					}
				}

				// Destroy already created singletons to avoid dangling resources.
				destroyBeans();

				// Reset 'active' flag.
				cancelRefresh(ex);

				// Propagate exception to caller.
				throw ex;
			}

spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java lines 628–652

What happens when an exception is thrown during refresh(): a warning log, stopping any Lifecycle beans already started, destroying the singletons created so far, cancelRefresh clearing the active flag, rethrowing the exception, and releasing the lock in finally. The red boxes are the exception coming in and going out. Everything built in between is cleaned up.

The Javadoc of ConfigurableApplicationContext.refresh() states the principle: after this method, "either all or no singletons at all should be instantiated" (lines 212–221). So if the tenth bean fails in step 11, the previous nine are destroyed too with destroyBeans() (line 645; implementation at lines 1246–1248), and cancelRefresh() clears active and the caches (lines 1025–1030). The exception is not swallowed; it is rethrown, so whoever called new AnnotationConfigApplicationContext(...) receives it. Note also that only steps 4 to 12 are inside the try: steps 1 to 3 create no beans, so there is nothing to clean up.

close() and doClose()

close() takes the lock, calls doClose(), and then removes the JVM shutdown hook if one was registered (lines 1146–1171). The real work is in doClose().

	protected void doClose() {
		// Check whether an actual close attempt is necessary...
		if (this.active.get() && this.closed.compareAndSet(false, true)) {
			// ...
			try {
				// Publish shutdown event.
				publishEvent(new ContextClosedEvent(this));
			}
			catch (Throwable ex) {
				logger.warn("Exception thrown from ApplicationListener handling ContextClosedEvent", ex);
			}

			// Stop all Lifecycle beans, to avoid delays during individual destruction.
			if (this.lifecycleProcessor != null) {
				try {
					this.lifecycleProcessor.onClose();
				}
				// ...
			}

			// Destroy all cached singletons in the context's BeanFactory.
			destroyBeans();

			// Close the state of this context itself.
			closeBeanFactory();

			// Let subclasses do some final clean-up if they wish...
			onClose();

			// Reset common introspection caches to avoid class reference leaks.
			resetCommonCaches();
			// ...

			// Reset internal delegates.
			this.applicationEventMulticaster = null;
			this.messageSource = null;
			this.lifecycleProcessor = null;

			// Switch to inactive.
			this.active.set(false);
		}
	}

spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java lines 1182–1233

The flow of close(): take the lock, and only if active and not yet closed, publish ContextClosedEvent, stop Lifecycle beans, destroy the singletons, close the BeanFactory, clear the caches and clear the active flag. The reverse of starting up. The event goes out first, whatever was running is stopped, and only then are the beans destroyed.

The order is the reverse of startup. Step 12 was start() → ContextRefreshedEvent; closing is ContextClosedEvent → stop() (onClose() calls stopBeans — DefaultLifecycleProcessor lines 336–339) → destroy the beans. The news that "we are closing" is announced while the beans are still alive; what was running is stopped; only then is anything destroyed. The if at the top sets closed with compareAndSet, so a second close() passes through quietly.

The destroySingletons() that destroyBeans() calls walks the list of beans that have a destruction callback from the end (DefaultSingletonBeanRegistry lines 703–705). Beans enter that list in creation order (AbstractAutowireCapableBeanFactory line 640), so the bean created last is destroyed first.

Example: the order on close()

// An example written for this post (not Spring's code)
package ex.u05;

// ... (imports omitted)

public class CloseOrderExample {

    // Registered first — destroyed later
    static class Repository implements DisposableBean {
        @Override
        public void destroy() {
            System.out.println("[3] DisposableBean.destroy(): Repository");
        }
    }

    // Registered later — destroyed first
    static class Service {
        @PreDestroy
        void shutdown() {
            System.out.println("[3] @PreDestroy: Service");
        }
    }

    static class Starter implements SmartLifecycle {
        private boolean running;

        @Override
        public void start() {
            this.running = true;
        }

        @Override
        public void stop() {
            this.running = false;
            System.out.println("[2] SmartLifecycle.stop()");
        }

        @Override
        public boolean isRunning() {
            return this.running;
        }
    }

    static class ClosedListener implements ApplicationListener<ContextClosedEvent> {
        @Override
        public void onApplicationEvent(ContextClosedEvent event) {
            System.out.println("[1] ContextClosedEvent");
        }
    }

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
                Repository.class, Service.class, Starter.class, ClosedListener.class);
        System.out.println("-- close() begins");
        context.close();
        System.out.println("-- close() done: isActive=" + context.isActive() + ", isClosed=" + context.isClosed());
    }
}
-- close() begins
[1] ContextClosedEvent
[2] SmartLifecycle.stop()
[3] @PreDestroy: Service
[3] DisposableBean.destroy(): Repository
-- close() done: isActive=false, isClosed=true

You can see the order event → stop() → destruction, and that Service, registered after Repository, is destroyed first. This example too was run on Spring Framework 7.0.9.

Easy to get wrong

If you want to go deeper

Things this post only named in passing. The 7.x restart() and pause() with their ContextRestartedEvent and ContextPausedEvent (ConfigurableApplicationContext lines 234–244). The tail of publishEvent, which also propagates the event to the parent context (lines 453–460). isStartupShutdownThreadStuck(), which works out whether the startup thread is stuck in a System.exit when the shutdown hook cannot get the lock (lines 1118–1136). At the start of step 11, the bootstrapExecutor (an executor that creates beans on background threads, lines 948–952) and BeanFactoryInitializer (lines 969–972). The LoadTimeWeaver in steps 3 and 5 (a device that rewrites bytecode as classes load, lines 757–761). refreshForAotProcessing, the AOT alternative to refresh() (GenericApplicationContext, Part 16). ApplicationStartup, which times startup with StartupStep (line 587). And the insides of Environment, MessageSource and the event multicaster, which Part 12 covers.

Recap

Concept In one line
ApplicationContext Two BeanFactory interfaces plus Environment, MessageSource, event publishing and resource loading. Read-only
ConfigurableApplicationContext Adds refresh(), close() and Lifecycle: the side that starts and stops
AbstractApplicationContext Implements the above. Does not inherit BeanFactory; holds one inside and delegates
refresh() Twelve steps: prepare (1–3) → process the blueprints (4–6) → set up the extras (7–10) → create beans and start running (11–12)
Step 3 prepareBeanFactory The Aware post-processor, the listener detector, and the registrations that make @Autowired ApplicationContext work
Steps 5 and 6 BeanFactoryPostProcessors are run here; BeanPostProcessors are only registered here
Early events Events published between step 1 and step 10 are held back and delivered in step 10
Step 11 freezeConfiguration then preInstantiateSingletons; SmartInitializingSingleton at the end
Step 12 Lifecycle.start(), then ContextRefreshedEvent
GenericApplicationContext BeanFactory created once in the constructor. Registration before refresh() is possible; a second refresh() throws
AbstractRefreshableApplicationContext A new BeanFactory on every refresh(), with the blueprints read again
Failure Destroys every singleton created, clears active, rethrows the exception
close() ContextClosedEvent → stop() → destroy the beans (reverse creation order) → clear active

What to look at next

In step 5 we passed in one line over "ConfigurationClassPostProcessor reads @Configuration classes and adds blueprints". Behind that line is how a single @Bean method becomes a BeanDefinition, how @ComponentScan searches the classpath, and why a @Configuration class is wrapped with CGLIB. Part 8: How @Configuration and @ComponentScan Become Blueprints covers it.

More posts