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

Optional-Checker-Paper/avaje-inject

 
 

Repository files navigation

Build JDK EA License Maven Central : avaje-inject Discord

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>

JDK 22+

In JDK 22+, annotation processors are disabled by default, you will need to add a flag in maven to re-enable the processor.

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-compiler-plugin</artifactId>
  <configuration>
    <compilerArgument>-proc:full</compilerArgument>
  </configuration>
</plugin>

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.Module;

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 Module with org.example.ExampleModule;
}

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

  • Aimed specifically for server-side development (rather than Android)
  • Supports lifecycle methods with @PostConstruct and @PreDestory
  • Supports @Factory and @Bean
  • Provides API to obtain all bean instances that implement an interface
  • Provides API to obtain all bean instances that have an annotation
  • Integration with server-side web frameworks Javalin, Helidon

Spring DI comparison

Spring Avaje
@Component, @Service, @Repository @Singleton
FactoryBean<T> Provider<T>
@Inject, @Autowired @Inject
@Autowired(required=false) @Inject @Nullable or @Inject Optional<T>
@PostConstruct @PostConstruct
@PreDestroy @PreDestroy
@Configuration and @Bean @Factory and @Bean
@Conditional @RequiresBean and @RequiresProperty
@Primary @Primary
@Secondary @Secondary
@Profile @Profile

Generated Code

DI classes

DI classes will be generated to call the constructors for annotated type/factory methods. Below is the class generated for the Example class in the above quickstart.

@Generated("io.avaje.inject.generator")
public final class Example$DI  {

  /**
   * Create and register Example.
   */
  public static void build(Builder builder) {
    if (builder.isAddBeanFor(Example.class)) {
      var bean = new Example(builder.get(DependencyClass.class,"!d1"), builder.get(DependencyClass2.class,"!d2"));
      builder.register(bean);
      // depending on the type of bean, callbacks for field/method injection, and lifecycle support will be generated here as well.
    }
  }
}

Generated Wiring Class

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

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

  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);
  }
}

About

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

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Java 99.5%
  • Shell 0.5%