Skip to content

Evaluate Java's CDI standard on lazy injectsion #260

Closed

Description

@michalharakal

CDI/JSR-330

Activity

  1. michalharakal commented on Dec 16, 2025

    @michalharakal
    ContributorAuthor

    CDI/JSR-330 Integration Analysis for Shared Neural Network Module

    Introduction

    The MNIST-CNN Integration Module design outlines a Kotlin Multiplatform component (skainet-int-mnist-cnn) that provides a shared, singleton-like deep neural network (the MNIST CNN) to consuming applications. It encapsulates model loading (from a GGUF file resource) and inference, exposing a MnistCnnShared API with coroutine/Flow-based state (Loading, Ready, Running, etc.).

    The question is whether this approach aligns with standard dependency injection practices, specifically JSR-330 (Javax Inject) and CDI (Contexts and Dependency Injection). In other words:

    • Can the idea of a shared, reusable neural network instance be implemented in compliance with JSR-330/CDI standards?
    • What adjustments or benefits would that bring?

    Understanding CDI and JSR-330

    JSR-330 (Dependency Injection for Java)

    JSR-330 is a specification that defines a minimal set of annotations and interfaces:

    • Annotations: @Inject, @Singleton, @Named, @Qualifier
    • Interfaces: Provider<T>

    JSR-330 does not provide an implementation by itself. Frameworks such as CDI containers, Guice, Dagger, and Spring implement these annotations.

    Key points:

    • Dependencies annotated with @Inject are supplied by the DI framework.

    • By default, unscoped dependencies are often created as new instances per injection site.

    • To share a single instance across the application, the object is marked with a singleton scope, such as:

      • @Singleton (JSR-330)
      • @ApplicationScoped (CDI)

    Sources:

    • mkyong.com
    • openliberty.io

    CDI (Contexts and Dependency Injection)

    CDI is the Java/Jakarta EE standard for dependency injection (Jakarta CDI, formerly JSR-299/346). CDI:

    • Builds on JSR-330

    • Adds well-defined scopes and contexts:

      • @ApplicationScoped
      • @RequestScoped
      • others
    • Supports lifecycle callbacks, producer methods, events, and interceptors

    An @ApplicationScoped bean:

    • Lives for the entire application runtime
    • Is effectively a singleton within the application context

    CDI encourages:

    • Loose coupling
    • Interface-driven design
    • Container-managed lifecycles for shared resources

    Shared Neural Network Module Overview

    The proposed MNIST CNN integration module is designed to make a pre-trained model easy to reuse across applications.

    Key Design Points

    • Single shared instance (MnistCnnShared) manages the model lifecycle and avoids loading the model multiple times.

    • Model loading abstraction via ModelParameterLoader (from io-core), with a default GGUF implementation (io-gguf).

      • This decouples the application from the file format.
    • Coroutine-friendly API:

      • Uses StateFlow to emit lifecycle states (Unloaded → Loading → Ready)
      • Inference is suspendable via run()
    • Manual lifecycle management:

      • Explicit load() and unload() methods
      • Mutex ensures thread-safe access
    • Kotlin Multiplatform support:

      • JVM (Android/server)
      • Potential iOS and JS targets
      • Platform-specific resource loading via expect/actual
    • Low coupling for consumers:

      • Apps do not manage model instantiation or loading logic
      • They interact only with a high-level API

    This design already follows core dependency injection principles by centralizing construction and configuration.


    Alignment with Dependency Injection Principles

    Singleton Mapping

    The idea of a single shared neural network instance maps directly to a singleton-scoped bean:

    • CDI: @ApplicationScoped
    • JSR-330: @Singleton

    This ensures:

    • One model instance per application
    • Reuse across all consumers

    From a DI perspective, MnistCnnShared is a classic heavy service singleton.


    Decoupling and Dependency Injection

    The module already abstracts model loading behind the ModelParameterLoader interface.

    In a DI setup:

    • ModelParameterLoader can be injected

    • Different implementations (GGUF, ONNX, remote loader) can be swapped without changing consumers

    • Selection can be handled via:

      • DI bindings
      • @Named or custom qualifiers

    This aligns perfectly with JSR-330’s principle of coding to interfaces.


    Providing the Shared Model via DI

    There are two standard DI patterns applicable here.

    1. Direct Injection

    Annotate MnistCnnShared:

    @Singleton
    class MnistCnnShared @Inject constructor(
        execContext: ExecutionContext,
        loader: ModelParameterLoader
    )

    Consumers then inject it:

    @Inject lateinit var mnistModel: MnistCnnShared

    The DI container:

    • Creates the instance
    • Ensures it is shared (singleton)
    • Injects dependencies automatically

    2. Producer / Factory Bean (CDI)

    In CDI, a producer method can create the instance:

    @Produces
    @ApplicationScoped
    fun buildMnistModel(): MnistCnnShared { ... }

    Notes:

    • Producers should return fully initialized objects
    • Producing null or partially initialized objects is discouraged (Stack Overflow guidance)
    • This approach is useful if creation logic is complex

    However, since the module already encapsulates creation and loading, direct injection is usually cleaner.


    Lifecycle and Initialization Strategies

    Eager Initialization

    Some DI containers eagerly create singletons:

    • Quarkus CDI creates @Singleton beans eagerly by default
    • Loading the model at startup guarantees readiness but may increase startup time

    Possible approach:

    • Trigger loading during application startup
    • Accept longer startup time

    Lazy Initialization via Provider

    JSR-330 provides Provider<T>:

    @Inject lateinit var modelProvider: Provider<MnistCnnShared>
    • The instance is created only when get() is called
    • Still respects singleton scope
    • Ideal for expensive resources

    This allows deferring model loading until first use.


    Manual Trigger with Injection

    A hybrid approach:

    • Inject MnistCnnShared
    • Manually call load() when appropriate (e.g., when a UI screen appears)

    Benefits:

    • Preserves progress reporting via StateFlow
    • Keeps DI responsible only for instantiation and sharing

    State Management and Thread Safety

    DI does not manage internal state or concurrency.

    Key points:

    • Singleton scope ensures only one instance exists
    • It does not serialize method calls
    • Internal synchronization (mutex) is still required

    The current design:

    • Uses a mutex to prevent concurrent load, run, and unload
    • Maintains internal state via StateFlow

    This is fully compatible with CDI/JSR-330.


    Multiplatform Integration

    JVM / Android

    • Works naturally with:

      • CDI
      • Dagger / Hilt
      • Spring
    • Can be injected into:

      • ViewModels
      • Controllers
      • REST endpoints

    Example (Hilt):

    @Provides
    @Singleton
    fun provideMnistModel(...): MnistCnnShared

    Non-JVM Targets (iOS, JS)

    • No standard CDI container
    • Instance managed manually
    • Module remains usable because it is self-contained

    DI is an optional integration, not a requirement.


    CDI Lifecycle Considerations

    Startup and Shutdown Hooks

    • @PostConstruct: optional eager or async initialization
    • @PreDestroy: ideal place to call unload()

    Example:

    @PreDestroy
    fun cleanup() {
        unload()
    }

    This ensures proper cleanup at application shutdown.


    Concurrency Considerations

    • Multiple concurrent callers may exist (e.g., server requests)
    • Current design serializes inference
    • DI does not affect this behavior

    Concurrency strategy remains an internal design decision.


    Benefits of Using Standard DI

    1. Standardized Singleton Management

    • Uses well-known annotations (@Singleton, @ApplicationScoped)
    • Easier for developers to understand intent

    2. Simpler Consumer Integration

    • Consumers just inject the model
    • No global references or manual instantiation
    • Lifecycle managed by the container

    3. Configuration Flexibility

    • Swap loaders or execution contexts via DI configuration
    • No changes required to consumers

    4. Lifecycle Alignment

    • Use @PostConstruct / @PreDestroy
    • Integrates cleanly with application lifecycle

    5. Improved Testability

    • Inject mock loaders or lightweight models
    • Configure different execution contexts for tests
    • No code changes required

    Potential Adjustments for CDI Compliance

    • Annotate MnistCnnShared with:

      • @ApplicationScoped (CDI)
      • or @Singleton (JSR-330)
    • Inject dependencies using @Inject

    • Optionally use qualifiers if multiple loaders exist

    • Optionally add lifecycle callbacks

    • Keep core module DI-agnostic if full multiplatform purity is desired

      • Applications can wrap or provide the instance via DI modules

    Conclusion

    The concept of a shared MNIST CNN model service fits naturally into CDI and JSR-330 paradigms.

    • A shared neural network is a textbook example of a singleton service

    • The design already follows DI principles:

      • Abstraction
      • Encapsulation
      • Separation of concerns
    • CDI/JSR-330 can formalize and standardize what the module already does

    Using DI:

    • Simplifies integration
    • Improves maintainability
    • Aligns lifecycle management with application standards

    Ultimately, adopting CDI/JSR-330 does not change the design’s goals—it enhances them by embedding the module in a widely understood and proven architectural pattern.


    Sources

    • JSR-330 Specification Highlights – Scope and Singleton usage
      mkyong.com

    • Open Liberty Documentation – ApplicationScoped beans as singletons in CDI
      openliberty.io

    • Medium (R. Ali) – JSR-330 Provider for lazy or controlled initialization
      medium.com

    • Quellen

    Sources:
    JSR-330 Specification Highlights – Scope and Singleton usage
    mkyong.com
    Open Liberty Documentation – ApplicationScoped beans as singletons in CDI
    openliberty.io
    Medium (R. Ali) – JSR-330 Provider for lazy or controlled initialization
    medium.com
    Quellenangaben

    Spring 3 and JSR-330 @Inject and @nAmed example - Mkyong.com

    https://mkyong.com/spring3/spring-3-and-jsr-330-inject-and-named-example/

    Spring 3 and JSR-330 @Inject and @nAmed example - Mkyong.com

    https://mkyong.com/spring3/spring-3-and-jsr-330-inject-and-named-example/

    RESTful Web Services integration with CDI :: Open Liberty Docs

    https://openliberty.io/docs/latest/jaxrs-integration-cdi.html

    java - CDI Produce Instance Created At Runtime - Stack Overflow

    https://stackoverflow.com/questions/43222650/cdi-produce-instance-created-at-runtime

    Introduction to Contexts and Dependency Injection (CDI) - Quarkus

    https://quarkus.io/guides/cdi

    Choosing Between dagger.Lazy and javax.inject.Provider: A Developer’s Guide | by Rahil Ali | Medium

    https://medium.com/@rahilali1991/choosing-between-dagger-lazy-and-javax-inject-provider-a-developers-guide-af448a237381

    Choosing Between dagger.Lazy and javax.inject.Provider: A Developer’s Guide | by Rahil Ali | Medium

    https://medium.com/@rahilali1991/choosing-between-dagger-lazy-and-javax-inject-provider-a-developers-guide-af448a237381
    Alle Quellen

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions