Dependency injection via APT (source code generation) ala "Server-Side Dagger DI"
APT-based dependency injection for server-side developers - https://avaje.io/inject
<dependency>
<groupId>io.avaje</groupId>
<artifactId>avaje-inject</artifactId>
<version>${avaje.inject.version}</version>
</dependency>
<dependency>
<groupId>io.avaje</groupId>
<artifactId>avaje-inject-generator</artifactId>
<version>${avaje.inject.version}</version>
<scope>provided</scope>
</dependency>
@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();
}
}
BeanScope beanScope = BeanScope.builder().build()
Example ex = beanScope.get(Example.class);
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;
}
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);
}
}
avaje-inject-test
and @InjectTest
@PostConstruct
and @PreDestroy
@Factory
and @Bean
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 |