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