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:

  1. Retrieves all Spring beans annotated with the specific DDD annotation via applicationContext.getBeansWithAnnotation()
  2. Invokes the corresponding IRegistryAware implementation (e.g., DomainDef, StepDef, RouterDef) to register each bean
  3. 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 @Autowired dependencies within registry entries are satisfied before registration occurs
  • Thread-safe: The AtomicBoolean check 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 ApplicationContextAware and ApplicationListener interfaces.
  • RegistryFactory.register() is invoked exactly once inside DDDBootstrap#setApplicationContext, guarded by an AtomicBoolean.
  • The registration process scans for DDD annotations (Domain, Step, Router, etc.) and delegates to specific IRegistryAware handlers.
  • InternalIndexer.postIndexing() finalizes the registry immediately after bean registration completes.
  • The entire sequence finishes before the ContextRefreshedEvent fires, 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →