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

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

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