Skip to content

Dependency injection via APT (source code generation) ala "Server-Side Dagger DI"

License

Notifications You must be signed in to change notification settings

avaje/avaje-inject

Folders and files

NameName
Last commit message
Last commit date
Oct 30, 2024
Aug 28, 2024
Jan 30, 2025
Jan 30, 2025
Feb 24, 2025
Jan 30, 2025
Jan 30, 2025
Feb 24, 2025
Jan 7, 2025
Jan 30, 2025
Feb 24, 2025
Feb 24, 2025
Nov 7, 2018
Jan 6, 2025
Jun 19, 2023
Sep 9, 2020
Oct 17, 2024
Aug 29, 2024
Jan 30, 2025
Dec 11, 2024
Jul 19, 2024
Jan 30, 2025
Feb 17, 2025
Aug 29, 2024

Repository files navigation

Discord Build JDK EA License Maven Central javadoc

APT-based dependency injection for server-side developers - https://avaje.io/inject

Quick Start

1. Add avaje-inject as a dependency.

<dependency>
  <groupId>io.avaje</groupId>
  <artifactId>avaje-inject</artifactId>
  <version>${avaje.inject.version}</version>
</dependency>

2. Add avaje-inject-generator annotation processor as a dependency with provided scope.

<dependency>
  <groupId>io.avaje</groupId>
  <artifactId>avaje-inject-generator</artifactId>
  <version>${avaje.inject.version}</version>
  <scope>provided</scope>
</dependency>

3. Create a Bean Class annotated with @Singleton

@Singleton
public class Example {

 private DependencyClass d1;
 private DependencyClass2 d2;

  // Dependencies must be annotated with singleton,
  // or else be provided from another class annotated with @Factory
  public Example(DependencyClass d1, DependencyClass2 d2) {
    this.d1 = d1;
    this.d2 = d2;
  }
}

Example factory class:

@Factory
public class ExampleFactory {
  @Bean
  public DependencyClass2 bean() {
    return new DependencyClass2();
  }
}

4. Use BeanScope to wire and retrieve the beans and use them however you wish.

BeanScope beanScope = BeanScope.builder().build()
Example ex = beanScope.get(Example.class);

Java Module Usage

When working with Java modules you need to add a provides statement in your module-info.java with the generated class.

import io.avaje.inject.spi.InjectExtension;

module org.example {

  requires io.avaje.inject;
  // you must define the fully qualified class name of the generated classes. if you use an import statement, compilation will fail
  provides InjectExtension with org.example.ExampleModule;
}

Generated Wiring Class

The inject annotation processor determines the dependency wiring order and generates an AvajeModule class that calls all the generated DI classes.

@Generated("io.avaje.inject.generator")
@InjectModule
public final class ExampleModule implements AvajeModule {

  private Builder builder;

  @Override
  public Class<?>[] classes() {
    return new Class<?>[] {
      org.example.DependencyClass.class,
      org.example.DependencyClass2.class,
      org.example.Example.class,
      org.example.ExampleFactory.class,
    };
  }

  /**
   * Creates all the beans in order based on constructor dependencies. The beans are registered
   * into the builder along with callbacks for field/method injection, and lifecycle
   * support.
   */
  @Override
  public void build(Builder builder) {
    this.builder = builder;
    // create beans in order based on constructor dependencies
    // i.e. "provides" followed by "dependsOn"
    build_example_ExampleFactory();
    build_example_DependencyClass();
    build_example_DependencyClass2();
    build_example_Example();
  }

  @DependencyMeta(type = "org.example.ExampleFactory")
  private void build_example_ExampleFactory() {
    ExampleFactory$DI.build(builder);
  }

  @DependencyMeta(type = "org.example.DependencyClass")
  private void build_example_DependencyClass() {
    DependencyClass$DI.build(builder);
  }

  @DependencyMeta(
      type = "org.example.DependencyClass2",
      method = "org.example.ExampleFactory$DI.build_bean", // factory method
      dependsOn = {"org.example.ExampleFactory"}) //factory beans naturally depend on the factory
  private void build_example_DependencyClass2() {
    ExampleFactory$DI.build_bean(builder);
  }

  @DependencyMeta(
      type = "org.example.Example",
      dependsOn = {"org.example.DependencyClass", "org.example.DependencyClass2"})
  private void build_example_Example() {
    Example$DI.build(builder);
  }
}

Similar to Dagger

  • Uses Java annotation processing for dependency injection
  • Generates source code
  • Avoids any use of reflection or classpath scanning (so low overhead and fast startup)

Differences to Dagger

  • Specifically aimed for server-side development (rather than Android)
  • Supports "component testing" via avaje-inject-test and @InjectTest
  • Provides API to obtain all bean instances that implement an interface
  • Lifecycle methods with @PostConstruct and @PreDestroy
  • Spring-like factory classes with @Factory and @Bean
  • Conditional Wiring based on active profiles or existing beans/properties

DI Framework comparison

Avaje Dagger Spring
@Singleton @Singleton @Component, @Service, @Repository
Provider<T> Provider<T> FactoryBean<T>
@Inject @Inject @Inject, @Autowired
@Inject @Nullable or @Inject Optional<T> @Inject @Nullable @Autowired(required=false)
@Qualifier/@Named @Qualifier/@Named @Qualifier
@AssistFactory @AssistedFactory -
@PostConstruct - @PostConstruct
@PreDestroy - @PreDestroy
@Factory and @Bean - @Configuration and @Bean
@RequiresBean and @RequiresProperty - @Conditional
@Primary - @Primary
@Secondary - @Fallback
@InjectTest - @SpringBootTest