DDDBootstrap Initialization Sequence: When `RegistryFactory.register()` is Invoked in cp-ddd-framework
RegistryFactory.register() is invoked exactly once during DDDBootstrap#setApplicationContext, which occurs after Spring injects dependencies but before the ContextRefreshedEvent fires.
The DDDBootstrap class serves as the internal Spring bootstrapper for the funkygao/cp-ddd-framework, orchestrating the registration of all DDD-plus annotated components into the runtime registry. Understanding its initialization sequence is essential for debugging startup issues or extending the framework with custom registry entries.
Overview of the Bootstrap Architecture
DDDBootstrap implements two critical Spring interfaces: ApplicationContextAware and ApplicationListener<ContextRefreshedEvent>. This dual implementation allows it to hook into the Spring lifecycle at two distinct phases: pre-refresh configuration and post-refresh completion.
The companion class RegistryFactory (also a Spring @Component) performs the actual bean registration logic. It maintains two internal collections populated during its own initialization phase:
validRegistryEntries– Ordered list of handlers for core DDD annotations (Domain, Interceptor, etc.)validPrepareEntries– Map for plugin-type annotations (Partner, Extension)
Step-by-Step Initialization Sequence
The framework follows a strict seven-phase initialization process:
1. Component Scanning and Bean Creation
Spring scans the classpath and instantiates singleton beans for both DDDBootstrap and RegistryFactory. Both classes carry the @Component annotation, making them candidates for classpath scanning.
// DDDBootstrap.java, lines 24-27
@Component
public class DDDBootstrap implements ApplicationContextAware, ApplicationListener<ContextRefreshedEvent> {
// ...
}
2. RegistryFactory Preparation
Because RegistryFactory implements InitializingBean, Spring invokes afterPropertiesSet() immediately after dependency injection. This method populates the registry metadata that will be used later.
In RegistryFactory.java (lines 55-71), the method initializes validRegistryEntries with ordered IRegistryAware implementations and prepares validPrepareEntries for plugin annotations.
3. Dependency Injection
Spring injects the RegistryFactory instance into DDDBootstrap via the @Resource annotation:
// DDDBootstrap.java, lines 29-31
@Resource
private RegistryFactory registryFactory;
4. ApplicationContext Callback (The Registration Trigger)
When the Spring container is ready, it calls DDDBootstrap#setApplicationContext(ApplicationContext). This method uses an AtomicBoolean to guarantee once-only execution, then immediately delegates to the registry factory:
// DDDBootstrap.java, lines 37-50
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
if (!initialized.compareAndSet(false, true)) {
return; // Ensure single execution
}
// ...
registryFactory.register(applicationContext); // Critical registration point
DDDBootstrap.applicationContext = applicationContext;
}
This is the exact moment RegistryFactory.register() is invoked—after bean creation and injection, but before the context refresh completes.
5. RegistryFactory.register() Execution
The register() method (lines 30-40 in RegistryFactory.java) iterates over validRegistryEntries. For each entry, it:
- Retrieves all Spring beans annotated with the specific DDD annotation via
applicationContext.getBeansWithAnnotation() - Invokes the corresponding
IRegistryAwareimplementation (e.g.,DomainDef,StepDef,RouterDef) to register each bean - Triggers
InternalIndexer.postIndexing()to finalize internal indexes after all beans are processed
6. Post-Indexing Finalization
After register() completes, InternalIndexer.postIndexing() builds runtime indexes required for pattern matching and router resolution. This must happen while the context is still initializing to ensure all infrastructure is ready before user code executes.
7. ContextRefreshedEvent Completion
Once Spring finishes refreshing the ApplicationContext, it fires a ContextRefreshedEvent. DDDBootstrap receives this event and:
- Logs startup completion
- Optionally invokes
IStartupListener#onStartComplete()if a user-provided listener bean exists
This occurs in DDDBootstrap.java (lines 52-63), marking the end of the bootstrap sequence.
Critical Timing: When is RegistryFactory.register() Called?
RegistryFactory.register() executes exactly once during the setApplicationContext callback. This timing is deliberate and provides specific guarantees:
- Before bean usage: Registration completes before any user code can access DDD components via
ApplicationContext.getBean() - After dependency resolution: All
@Autowireddependencies within registry entries are satisfied before registration occurs - Thread-safe: The
AtomicBooleancheck prevents double execution in complex refresh scenarios
The call chain follows this strict order:
DDDBootstrap#setApplicationContext
→ RegistryFactory.register(applicationContext)
→ InternalIndexer.postIndexing()
Accessing DDD Beans After Bootstrapping
Once the sequence completes, you can safely retrieve DDD components through the static context reference maintained by DDDBootstrap:
// Obtain the Spring ApplicationContext stored during bootstrap
ApplicationContext ctx = DDDBootstrap.applicationContext();
// Retrieve a registered DDD+ bean by type
MyDomainService service = ctx.getBean(MyDomainService.class);
// Execute domain logic
service.doSomething();
This approach works because DDDBootstrap.applicationContext() returns the same context instance passed to RegistryFactory.register(), ensuring all annotated beans have been processed and indexed.
Summary
- DDDBootstrap coordinates the startup sequence using Spring's
ApplicationContextAwareandApplicationListenerinterfaces. RegistryFactory.register()is invoked exactly once insideDDDBootstrap#setApplicationContext, guarded by anAtomicBoolean.- The registration process scans for DDD annotations (Domain, Step, Router, etc.) and delegates to specific
IRegistryAwarehandlers. InternalIndexer.postIndexing()finalizes the registry immediately after bean registration completes.- The entire sequence finishes before the
ContextRefreshedEventfires, ensuring runtime readiness when user code begins execution.
Frequently Asked Questions
What prevents RegistryFactory.register() from being called multiple times?
An AtomicBoolean named initialized inside DDDBootstrap ensures the registration logic executes only once. When setApplicationContext is invoked, it attempts a compareAndSet(false, true) operation. If the bootstrapper has already run, the method returns immediately without re-invoking registryFactory.register().
How does the framework handle different types of DDD annotations during registration?
RegistryFactory maintains two distinct collections initialized in afterPropertiesSet(). The validRegistryEntries list handles core framework annotations like Domain and Interceptor, while validPrepareEntries manages plugin-oriented annotations such as Partner and Extension. Each entry maps to a specific IRegistryAware implementation that understands how to process its annotation type.
Can I access DDD beans before the ContextRefreshedEvent completes?
Yes. Beans registered during the RegistryFactory.register() phase are available immediately after that method returns, which occurs before ContextRefreshedEvent fires. However, you should rely on the standard DDDBootstrap.applicationContext() accessor to ensure you access the context after the static reference has been set in setApplicationContext().
What happens if a bean fails to register during the bootstrap sequence?
If an IRegistryAware implementation throws an exception during registration in RegistryFactory.register(), the exception propagates up through setApplicationContext, causing the Spring context initialization to fail fast. This prevents the application from starting in a partially configured state where some DDD components might be missing from the internal indexes.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →