skip to content

Walk through the registerBean overload that takes a Supplier and BeanDefinitionCustomizer varargs. What does each argument do and what can you customize?

level: middleimportance: must knowfreq 45%

answer

  1. instanceSupplier on RootBeanDefinition
  2. BeanDefinitionCustomizer = void customize(bd)
  3. setScope/setPrimary/setLazyInit
  4. customizers apply in order, last wins
  5. String-name overload for deterministic names

basics

~20 s

registerBean(Class, Supplier, BeanDefinitionCustomizer...): the Class is the bean type, the Supplier is a lambda that creates the instance, and each BeanDefinitionCustomizer is a lambda that tweaks the BeanDefinition — e.g. set scope, primary, or lazy.

solid answer

~40 s

The signature is registerBean(Class<T> beanClass, Supplier<T> supplier, BeanDefinitionCustomizer... customizers) on GenericApplicationContext. Internally it builds a RootBeanDefinition for the class, sets its instanceSupplier to your lambda, applies each customizer, and registers it (there is also a String-name overload; without a name Spring generates one). BeanDefinitionCustomizer is a functional interface — void customize(BeanDefinition bd) — so each customizer receives the freshly built definition and can call bd.setScope(SCOPE_PROTOTYPE), bd.setPrimary(true), bd.setLazyInit(true), bd.setAutowireCandidate(false), bd.setDescription(...), or set qualifiers/attributes. Customizers run in order, so later ones can override earlier ones. The Supplier owns instantiation, so it is where you inject collaborators manually — commonly by pulling them from the context or capturing them in the closure. This overload is the workhorse for reflection-free, explicit registration.

code

java · 13 lines
java
GenericApplicationContext ctx = new AnnotationConfigApplicationContext();

ctx.registerBean(
    "paymentClient",                     // explicit name
    PaymentClient.class,                  // bean type
    () -> new PaymentClient(apiKey),      // instance supplier (no autowiring)
    bd -> bd.setScope("singleton"),       // customizer 1
    bd -> bd.setPrimary(true),            // customizer 2
    bd -> bd.setLazyInit(true)            // customizer 3
);

ctx.refresh();
PaymentClient c = ctx.getBean(PaymentClient.class);

go deeper

for a junior

Name the three arguments and that customizers set scope/primary/lazy.

for a middle

Explain instanceSupplier, the functional BeanDefinitionCustomizer interface, ordering, and the name-generation overload.

for a senior

Discuss dependency-injection patterns with a supplier and casting to AbstractBeanDefinition for qualifiers/autowire mode.

for a principal

Weigh supplier-based control vs autowiring for AOT, and design guidance for registering same-type beans deterministically.

**Signature (on `GenericApplicationContext`)** ```java <T> void registerBean(Class<T> beanClass, Supplier<T> supplier, BeanDefinitionCustomizer... customizers) <T> void registerBean(String beanName, Class<T> beanClass, Supplier<T> supplier, BeanDefinitionCustomizer... customizers) ``` There are also supplier-less overloads (`registerBean(Class, Object... constructorArgs)`) that let the container instantiate and autowire. **Argument by argument** 1. **`beanClass`** — the type registered; used for type-based lookup/injection (`getBean(MyType.class)`, `@Autowired MyType`). A `RootBeanDefinition` is created for it. 2. **`supplier`** — a `Supplier<T>` set as the definition's **`instanceSupplier`**. When the bean is needed, Spring calls `supplier.get()` instead of instantiating via constructor reflection. This means **no autowiring of constructor args** — the supplier is fully responsible for building the object. 3. **`customizers`** — zero or more `BeanDefinitionCustomizer`. This is a functional interface: ```java @FunctionalInterface public interface BeanDefinitionCustomizer { void customize(BeanDefinition bd); } ``` Each is invoked with the built definition, in argument order. Typical calls: - `bd.setScope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)` / `"singleton"` - `bd.setPrimary(true)` - `bd.setLazyInit(true)` - `bd.setAutowireCandidate(false)` - `bd.setInitMethodName(...)` / `bd.setDestroyMethodName(...)` - `bd.setDescription(...)`, `bd.setAttribute(key, value)` - add qualifiers via `((AbstractBeanDefinition) bd).addQualifier(new AutowireCandidateQualifier(...))` **Name generation** — the no-name overload generates a bean name (the framework's `BeanNameGenerator` — typically the decapitalized simple class name). Use the String-name overload for a deterministic name (important when two beans share a type). **Ordering & override semantics** — customizers apply left-to-right; if two set the same property, the last wins. Registration itself must happen **before** `refresh()`. **How dependencies get in with a Supplier** — three common patterns: - Closure capture: `() -> new Svc(depAlreadyInScope)`. - Pull from context: `() -> new Svc(ctx.getBean(Dep.class))` (needs a reference to the context). - Use the supplier-less overload so Spring autowires instead. **Gotchas** - Passing a `Supplier` **disables constructor autowiring**; forgetting this leads to NPEs on collaborators you assumed were injected. - `BeanDefinitionCustomizer` cannot 'unset' the instanceSupplier's control over construction — it edits definition metadata, not the instantiation strategy. - Some rich customizations (qualifiers, autowire mode) require casting to `AbstractBeanDefinition`. - Prototype scope + supplier means the supplier runs on every `getBean` call. **When to use** — precise, code-driven registration where you want explicit scope/primary/lazy control without annotations; registering the same type multiple times under different names; AOT/native-friendly wiring.

  • If two customizers both call setScope, which wins?
    The last one in the varargs order, because customizers are applied left-to-right against the same BeanDefinition and simply overwrite the property.
  • How would you make a supplier-registered bean lazy and non-autowire-candidate?
    Pass customizers: bd -> bd.setLazyInit(true) and bd -> bd.setAutowireCandidate(false). Both edit the BeanDefinition metadata before registration.

saying these in an interview costs you the question

  • Believing the Supplier bean still gets constructor injection
  • Thinking BeanDefinitionCustomizer changes the instance rather than the definition
  • Assuming registration works after refresh()
  • Not knowing customizers are ordered / last-write-wins

context