Avaje Inject

repository·master·Indexed 43 days ago

https://github.com/avaje/avaje-inject

An annotation-processor-based dependency injection framework for server-side Java developers. It uses Java annotation processing (APT) to generate wiring code at compile time, eliminating the need for reflection and classpath scanning to ensure high performance, low overhead, and fast startup times for microservices.

Tokens
12.9K
Snippets
41
Records
62
Agent score
55%

What's inside Avaje Inject

  1. What is Avaje Inject?

    master

    Avaje Inject is an Annotation Processing Tool (APT) based dependency injection framework designed specifically for server-side developers. It uses Java annotation processing to generate source code at compile time, which avoids the use of reflection or classpath scanning. This results in low overhead and fast startup times, making it highly suitable for microservices.

    Key characteristics:

    • Compile-time DI: Uses APT to generate wiring code.
    • Zero Reflection: No reflection or classpath scanning is used at runtime.
    • Library-only: It is a lightweight DI library (~25k in size) with minimal dependencies.
  2. Compare Avaje-Inject with Dagger and Spring

    master

    Avaje-Inject is designed for server-side development. While similar to Dagger in its use of annotation processing and code generation to avoid reflection, it offers several features more common in Spring:

    • Lifecycle Support: Uses @PostConstruct and @PreDestroy.
    • Factory Pattern: Supports Spring-like @Factory and @Bean annotations.
    • Conditional Wiring: Supports @RequiresBean and @RequiresProperty.
    • Testing: Provides @InjectTest for component testing via avaje-inject-test.
    • Interface Retrieval: Provides an API to obtain all bean instances that implement a specific interface.
  3. Explore Avaje Inject documentation and guides

    master

    The Avaje Inject documentation is organized into a full library reference and a collection of step-by-step guides.

    • For comprehensive information on the API, annotations, use cases, and performance, refer to LIBRARY.md.
    • For specific tasks and implementation patterns, use the guides located in the guides/ directory.
  4. Compare Avaje Inject to other DI frameworks

    master

    vs Dagger

    • Target: Avaje is aimed at server-side development, whereas Dagger is often used for Android.
    • Lifecycle: Avaje supports @PostConstruct and @PreDestroy.
    • Annotations: Avaje supports @Factory and @Bean.
    • Collections: Avaje provides APIs to obtain all bean instances that implement a specific interface or have a specific annotation.
    • Integrations: Avaje integrates with server-side web frameworks like Rapidoid, Sparkjava, and Javlin.

    vs Micronaut DI

    • Generation: Avaje generates source code, while Micronaut generates bytecode.
    • Ordering: Avaje generates construction order at compile time for faster execution.
    • Footprint: Avaje aims to be a 'Library only' with minimal dependencies and a smaller footprint.

    vs Spring DI

    • Scope: Avaje Inject only supports Singleton scope.
    • Configuration: Avaje does not support @Value (it recommends using avaje-config).
    • Performance: Avaje uses APT-based code generation instead of reflection or scanning, leading to much lower startup times and costs, which is better suited for microservices.
  5. Conditionally register beans using Optional<T>

    master

    Factory methods can return Optional<T> to support conditional bean registration.

    • If the method returns Optional.of(instance), Avaje Inject registers the contained instance as a bean of type T.
    • If the method returns Optional.empty(), no bean of type T is registered.

    Important: An @Bean Optional<T> method does not register Optional<T> as the bean type; it conditionally registers the underlying type T. This allows consumers to inject Optional<T> to gracefully handle integrations that might be disabled based on configuration.

    @Factory
    class MetricsConfig {
    
      @Bean
      Optional<GraphiteReporter> graphiteReporter(Configuration config) {
        if (!config.enabled("metrics.graphite.enabled", false)) {
          return Optional.empty();
        }
        return Optional.of(GraphiteReporter.builder().build());
      }
    }
    
    @Singleton
    class MetricsReporter {
    
      private final Optional<GraphiteReporter> reporter;
    
      MetricsReporter(Optional<GraphiteReporter> reporter) {
        this.reporter = reporter;
      }
    }
  6. Control bean creation order via dependencies

    master

    Avaje Inject determines bean creation order based on real dependencies. To ensure one bean is initialized before another, include the dependency as a parameter in the @Bean factory method or the target class constructor.

    Avoid creating artificial or reverse dependencies solely to force an order, as this can lead to circular dependency cycles. The dependency direction should always reflect the actual requirement for initialization.

    @Factory
    class DatabaseConfig {
    
      @Bean
      Database database(OpenTelemetry openTelemetry, DataSource dataSource) {
        return Database.builder()
          .dataSource(dataSource)
          .build();
      }
    }
  7. How avaje-inject works with GraalVM

    master

    avaje-inject uses the inject-generator annotation processor to perform compile-time code generation. This approach avoids reflection-based wiring, making it compatible with the native image compiler. The generator produces the following artifacts:

    • *Module classes: Explicit wiring code that constructs and injects beans in dependency order without reflection.
    • *$DI classes: Per-bean classes containing explicit constructor calls, field injection, and lifecycle callbacks.
    • META-INF/services/io.avaje.inject.spi.InjectExtension: A service file used by ServiceLoader to discover the generated module at startup.

    Because the wiring is expressed as plain Java source code, it is treated as standard code by the native image compiler.

  8. How @Lazy beans are handled in GraalVM

    master
    For beans marked with @Lazy, avaje-inject handles construction deferral at compile time. The annotation processor generates a proxy subclass that defers bean construction until the first use. This avoids the need for dynamic proxies or runtime bytecode generation, which are typically problematic for GraalVM native images.
  9. How AOP proxies work with GraalVM

    master

    When avaje-inject generates AOP proxies (for example, for @Aspect beans), they are generated as plain source code. However, these proxies use reflection to obtain the Method included in the io.avaje.inject.aop.Invocation.

    To support this, the generator automatically produces a reflect-config.json resource file located in META-INF/native-image. This file instructs the native image compiler to include the necessary reflection metadata required for AOP proxies to function at runtime.

  10. Configure bean scopes

    master

    You can control the lifecycle of a bean using scope annotations:

    • @Singleton: Creates a single instance for the application (this is the default behavior for many configurations).
    • @Prototype: Creates a new instance every time the bean is requested/injected.
    @Singleton          // One instance (default)
    public class Single { }
    
    @Prototype          // New instance each time
    public class Multi { }