At the API level, how do you wire a parent context, and how does the bean factory implement hierarchical lookup? Cover ConfigurableApplicationContext.setParent and HierarchicalBeanFactory.getParentBeanFactory.
answer
- setParent BEFORE refresh (ConfigurableApplicationContext)
- prepareBeanFactory → setParentBeanFactory(parent factory)
- getParentBeanFactory() / containsLocalBean() on HierarchicalBeanFactory
- doGetBean: local → parent delegation
- Events bubble UP (4.2+); getBeansOfType stays local
basics
~10 sCall ConfigurableApplicationContext.setParent(parent) before refresh(). Internally the child's BeanFactory records the parent via setParentBeanFactory, and HierarchicalBeanFactory.getParentBeanFactory() exposes it. Lookups check locally, then delegate to the parent factory.
solid answer
~40 s`ConfigurableApplicationContext` (the writable subinterface of `ApplicationContext`) declares `setParent(ApplicationContext)`. You call it on the child **before `refresh()`**. During `refresh()`/`prepareBeanFactory`, the child copies the parent's *bean factory* into its own via `setParentBeanFactory(parent.getBeanFactory())`, so the linkage lives at the `BeanFactory` level. Every context's factory implements `HierarchicalBeanFactory`, which exposes `getParentBeanFactory()` and `containsLocalBean(name)`. On `getBean`, `AbstractBeanFactory.doGetBean` resolves locally first, then, if absent and a parent exists, delegates to `getParentBeanFactory().getBean(...)`, walking up the tree. Key subtleties: the parent must be fully initialized/refreshed first; the relationship is set once and effectively immutable after refresh; single-bean lookups and `@Autowired` span ancestors, but `getBeansOfType` doesn't unless you use `BeanFactoryUtils.beansOfTypeIncludingAncestors`. `SpringApplicationBuilder.parent()/child()` is the Boot-friendly builder for this.
code
java · 17 lines// Manual wiring
var parent = new AnnotationConfigApplicationContext(ParentConfig.class); // refreshed
var child = new AnnotationConfigApplicationContext();
child.setParent(parent); // ConfigurableApplicationContext.setParent, BEFORE refresh
child.register(ChildConfig.class);
child.refresh();
ConfigurableListableBeanFactory bf = child.getBeanFactory();
bf.getParentBeanFactory(); // -> parent's factory (HierarchicalBeanFactory)
bf.containsLocalBean("clock"); // true only if defined in the child itself
// Spanning ancestors for a collection lookup:
Map<String, Clock> all =
BeanFactoryUtils.beansOfTypeIncludingAncestors(bf, Clock.class);
// Boot-friendly builder equivalent:
// new SpringApplicationBuilder(ParentConfig.class).child(ChildConfig.class).run(args);go deeper
Know that setParent links the contexts and lookups delegate upward; details optional.
Explain setParent-before-refresh and getParentBeanFactory delegation in doGetBean.
Add containsLocalBean, the getBeansOfType-vs-getBean asymmetry, and event bubbling; prefer SpringApplicationBuilder in Boot.
Discuss lifecycle ordering (parent first, close child first), immutability after refresh, autowiring-includes-ancestors internals, and when a hierarchy is justified vs a flat context.
## The API surface - **`ApplicationContext.getParent()`** — read-only accessor returning the parent context (or `null`). - **`ConfigurableApplicationContext.setParent(ApplicationContext parent)`** — the writable subinterface (implemented by `AbstractApplicationContext`, `AnnotationConfigApplicationContext`, etc.) lets you set the parent. Must be called **before `refresh()`**. - **`HierarchicalBeanFactory`** — implemented by every context's underlying factory. Adds: - `getParentBeanFactory()` → the parent `BeanFactory` or `null`. - `containsLocalBean(String name)` → true only if defined **in this factory**, ignoring ancestors (useful to test local vs inherited presence). - **`ConfigurableBeanFactory.setParentBeanFactory(BeanFactory)`** — the factory-level setter the context uses internally. ## Wiring sequence 1. You build and **refresh the parent** context (it must be usable). 2. On the child, call `setParent(parent)`. 3. Call the child's `refresh()`. Inside `AbstractApplicationContext.refresh()` → `obtainFreshBeanFactory()` → `prepareBeanFactory()`, the child sets its factory's parent: `beanFactory.setParentBeanFactory(getInternalParentBeanFactory())`, which returns `parent.getAutowireCapableBeanFactory()` (i.e., the parent's `DefaultListableBeanFactory`). Now the linkage lives at the factory level. ## Lookup algorithm `AbstractBeanFactory.doGetBean(name, ...)`: 1. Transform aliases/factory-dereference prefixes. 2. Check this factory's **singleton cache** and **bean definitions**. 3. If **not found locally** and `getParentBeanFactory() != null`, delegate: `parentBeanFactory.getBean(originalName, ...)`. 4. Recurse upward until resolved or the root throws `NoSuchBeanDefinitionException`. This is why `getBean(String)`, `getBean(Class)`, and single-value `@Autowired` see ancestor beans. Autowiring candidate discovery (`DefaultListableBeanFactory.findAutowireCandidates`) explicitly uses `BeanFactoryUtils.beanNamesForTypeIncludingAncestors`, so injected **collections** span the hierarchy too. ## The collection asymmetry `ListableBeanFactory` collection methods — `getBeanDefinitionNames()`, `getBeansOfType()`, `getBeanNamesForType()` — introspect **only the local factory** and **exclude ancestors by design**. To span, call `BeanFactoryUtils.beansOfTypeIncludingAncestors(factory, type)` / `beanNamesForTypeIncludingAncestors(...)`. This is the single most surprising hierarchy gotcha. ## Constraints and edge cases - **Immutability after refresh:** you cannot meaningfully re-parent a live context; set it before refresh. - **Parent lifecycle:** the child depends on the parent, so **start/refresh the parent first** and **close the child before the parent** (closing a parent doesn't cascade-close children automatically; manage lifecycles or use a builder). - **Event propagation:** since Spring 4.2, an `ApplicationEvent` published in a **child** propagates **up** to ancestors (a parent listener can hear child events), but events published in the **parent** are **not** pushed down to children. - **Duplicate singletons / name shadowing:** as with any hierarchy, same-typed beans in both levels create two instances; same-named child beans shadow the parent for child lookups. ## Boot-friendly construction `new SpringApplicationBuilder(ParentConfig.class).child(ChildConfig.class).run(args)` (or `.parent(...)`) builds the hierarchy and manages lifecycle/ordering for you — preferable to hand-wiring `setParent` in Boot apps. Spring Cloud's classic **bootstrap context** is exactly a parent to the main application context.
- Can you change a context's parent after it has been refreshed?No, not meaningfully. setParent must be called before refresh(); the factory records the parent during prepareBeanFactory. Re-parenting a live context isn't supported — build a new context if the hierarchy must change.
- If a bean in the child publishes an ApplicationEvent, can a listener registered only in the parent receive it?Yes, since Spring 4.2 events published in a child context propagate upward to ancestor contexts. The reverse is not true: parent-published events are not delivered down to children.
- How do you correctly close a parent-child pair?Close the child before the parent, since the child depends on parent beans. Closing the parent does not automatically close its children, so manage the order explicitly or let SpringApplicationBuilder handle the lifecycle.
saying these in an interview costs you the question
- Calling setParent after refresh() and expecting it to take effect.
- Assuming getBeansOfType on the child returns ancestor beans (it doesn't; use BeanFactoryUtils).
- Thinking parent events propagate down to children (only child→parent propagation exists, since 4.2).
- Believing closing the parent auto-closes children.