Walk through how `BeanDefinitionMethodGenerator` and `BeanRegistrationCodeFragments` produce the generated registration code, and how you'd customize a fragment.
answer
- BeanDefinitionMethodGenerator emits the per-bean method
- delegates to BeanRegistrationCodeFragments (newDefinition/properties/instanceSupplier)
- BeanRegistrationAotProcessor → BeanRegistrationAotContribution
- withCustomCodeFragments(decorate defaults)
- register via aot.factories; processor bean excluded at runtime
basics
~10 sFor each bean, BeanDefinitionMethodGenerator emits a method that builds the BeanDefinition. It delegates the pieces (new definition, properties, instance supplier) to BeanRegistrationCodeFragments. You customize output by contributing an AOT processor that wraps/overrides those fragments.
solid answer
~40 sDuring AOT, `BeanRegistrationsAotContribution` iterates registered beans; for each it uses a `BeanDefinitionMethodGenerator` to emit the static method that returns the bean's `BeanDefinition`. That generator doesn't hardcode the code — it delegates to `BeanRegistrationCodeFragments`, an SPI with methods like `generateNewBeanDefinitionCode`, `generateSetBeanDefinitionPropertiesCode`, and `generateInstanceSupplierCode`. Default fragments produce the `RootBeanDefinition` + `BeanInstanceSupplier` shown in the generated `*__BeanDefinitions`. To customize, you implement a `BeanRegistrationAotProcessor` that returns a `BeanRegistrationAotContribution`; in its `applyTo` you register code via `BeanRegistrationCode`, and you can override fragments using `withCustomCodeFragments(defaultFragments -> new MyFragments(defaultFragments))` — decorating the defaults so you only change, say, the instance supplier while inheriting property generation. This is how Spring itself handles special beans (e.g., configuration properties, JPA).
code
java · 25 linesclass TracingBeanRegistrationAotProcessor implements BeanRegistrationAotProcessor {
@Override
public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) {
if (!registeredBean.getBeanClass().isAnnotationPresent(Traced.class)) {
return null; // not our concern -> no contribution
}
return BeanRegistrationAotContribution.withCustomCodeFragments(defaultFragments ->
new BeanRegistrationCodeFragmentsDecorator(defaultFragments) {
@Override
public CodeBlock generateInstanceSupplierCode(
GenerationContext generationContext,
BeanRegistrationCode beanRegistrationCode,
boolean allowDirectSupplierShortcut) {
// start from the default supplier, then wrap the instance
CodeBlock delegate = super.generateInstanceSupplierCode(
generationContext, beanRegistrationCode, allowDirectSupplierShortcut);
return CodeBlock.of("$L /* traced */", delegate);
}
});
}
}
// Registered in META-INF/spring/aot.factories:
// org.springframework.beans.factory.aot.BeanRegistrationAotProcessor=\
// com.example.TracingBeanRegistrationAotProcessorgo deeper
Not expected to know the fragment SPI; awareness that generation is pluggable is enough.
Know that generation delegates to code fragments and that AOT processors can customize it, at a high level.
Name BeanDefinitionMethodGenerator, BeanRegistrationCodeFragments methods, BeanRegistrationAotProcessor/Contribution, and the decorate-defaults pattern.
Discuss version-stability risk of these SPIs, when a library should ship an AOT processor + hints, and aot.factories vs bean-based registration trade-offs.
## The generation pipeline AOT context processing is driven by `ApplicationContextAotGenerator`, which asks the `BeanFactory` for `BeanFactoryInitializationAotContribution`s. Bean registration specifically flows through: 1. **`BeanRegistrationsAotProcessor`** → produces a **`BeanRegistrationsAotContribution`** holding one entry per bean to register. 2. For each bean, a **`BeanDefinitionMethodGenerator`** generates a `static` method (in the `*__BeanDefinitions` class) that returns a `BeanDefinition`. 3. The generator does not write the code inline — it delegates the individual chunks to **`BeanRegistrationCodeFragments`**. ## `BeanRegistrationCodeFragments` — the customizable SPI `org.springframework.beans.factory.aot.BeanRegistrationCodeFragments` is an interface whose methods each emit a `CodeBlock` for one concern: - `generateNewBeanDefinitionCode(...)` — creates the `RootBeanDefinition`/`BeanDefinition` (target type). - `generateSetBeanDefinitionPropertiesCode(...)` — sets scope, role, lazy, primary, property values, qualifiers, etc. - `generateSetBeanInstanceSupplierCode(...)` / `generateInstanceSupplierCode(...)` — wires the `BeanInstanceSupplier` (constructor/factory-method invocation). - `generateReturnCode(...)` — returns the definition. The default implementation is obtained via `BeanRegistrationCodeFragments.DEFAULTS` / the framework's `DefaultBeanRegistrationCodeFragments`. Because it's an interface, custom fragments typically **decorate** the defaults (delegate for the parts you don't touch). ## `BeanRegistrationCode` — the sink `BeanRegistrationCode` is the callback surface handed to a contribution: it exposes the target `GeneratedClass`/method name space and `addInstancePostProcessor(...)` for wiring injection. Your contribution writes into it. ## Customizing: `BeanRegistrationAotProcessor` To influence a specific bean's generated code you implement: ```java class MyBeanRegistrationAotProcessor implements BeanRegistrationAotProcessor { @Override public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) { if (!isMyKind(registeredBean)) return null; // opt out return BeanRegistrationAotContribution.withCustomCodeFragments(defaultFragments -> new MyCodeFragments(defaultFragments)); } } ``` `BeanRegistrationAotContribution.withCustomCodeFragments(...)` gives you the default fragments to wrap. Your `MyCodeFragments extends BeanRegistrationCodeFragmentsDecorator` overrides only the method(s) you care about (e.g., a custom instance supplier) and calls `super` for the rest. Alternatively `BeanRegistrationAotContribution.ofBeanRegistrationCodeFragmentsCustomizer(...)`-style helpers and `applyTo(GenerationContext, BeanRegistrationCode)` let you also register `RuntimeHints`. ## Registering the processor `BeanRegistrationAotProcessor` (and the factory-level `BeanFactoryInitializationAotProcessor`) are discovered from `META-INF/spring/aot.factories` (the AOT analog of `spring.factories`), or a processor can itself be a bean implementing the interface — Spring detects such beans and, importantly, **excludes them from the runtime context** (they're build-time only). ## When to use - Framework/library authors who register beans in unusual ways (special instance suppliers, generated proxies, metadata) and need the AOT output to reflect that. - Adding `RuntimeHints` tied to a specific bean's registration. - Rarely needed in ordinary applications — Spring's built-in processors already cover components, `@Bean` methods, config properties, JPA, etc. ## Gotchas - Always **decorate** the default fragments; re-implementing all methods from scratch is fragile across Spring versions. - These are **internal-ish SPIs** — signatures can change between minor versions; pin to a Spring version and re-test. - Returning `null` from `processAheadOfTime` means "no contribution" — don't throw for beans you don't handle. - A processor bean is build-time only; don't rely on it existing at runtime.
- Why decorate the default fragments instead of implementing `BeanRegistrationCodeFragments` from scratch?The default implementation encodes a lot of correct behavior (property values, qualifiers, supplier shortcuts, hint registration) that changes across Spring versions. Decorating lets you override one concern and inherit the rest, staying compatible and less error-prone.
- How is a `BeanRegistrationAotProcessor` discovered, and does it exist at runtime?It's registered in `META-INF/spring/aot.factories` (or detected as a bean implementing the interface). When it's a bean, Spring runs it at build time and excludes it from the runtime context — AOT processors are build-time only.
saying these in an interview costs you the question
- Confusing `aot.factories` with `spring.factories` (different files/purposes)
- Thinking a BeanRegistrationAotProcessor runs at application runtime
- Claiming BeanDefinitionMethodGenerator hardcodes the output (it delegates to fragments)
- Throwing exceptions for unhandled beans instead of returning null