JSR-330: Using reflexivity to auto-inject

Introduction

Kodein-DI offers a module that implements reflexivity injection based on the JSR-330 javax.inject.* annotations. There are two reasons to use this module:

  • You are moving a code base from a Java injection library (such as Guice or Dagger) and want the Java code to work the same while there still is injected java code.

  • You want to easily use Kodein-DI in a Java code.

  • That’s it!

Using this module with Kotlin code means adding a lot of reflexivity at run-time that can easily be avoided in Kotlin (but not in Java).

Every-thing that is described here is a lot less performant than using classic DI injection methods. PLEASE DO NOT USE ON KOTLIN CLASSES. Kittens will die painfully if you do!

Install

With Gradle

implementation("org.kodein.di:kodein-di-jxinject-jvm:7.16.0")

With Maven

<dependency>
    <groupId>org.kodein.di</groupId>
    <artifactId>kodein-di-jxinject-jvm</artifactId>
    <version>7.16.0</version>
</dependency>

Import the module.

In your di binding configuration add the jxInjectorModule

Example: importing the JxInjector module
val di = DI {
    import(jxInjectorModule)
    /* Other bindings */
}

JavaX injections

Constructor injection

You can create a new instance of a given class, provided that:

  • The class has only one constructor

  • Or the class have one of its constructors annotated with @Inject.

It is a good practice, however, to always have an @Inject constructor, even if it is the only constructor.

Example: Java class that can be automatically created with injections
public class MyJavaController {
    @Inject
    public MyJavaController(Connection connection, FileSystem fs) {
        /* ... */
    }
    /* ... */
}

You can then create instances of such classes by using di.jx in Kotlin, or Jx.of(di) in Java.

Example: Creating a new instance in Kotlin
val controller = di.jx.newInstance<MyJavaController>()
Example: Creating a new instance in Java
MyJavaController controller = Jx.of(di).newInstance(MyJavaController.class);

Field injection

You can inject fields of a class by annotating them with @Inject.

Example: Java class whose fields can be automatically injected.
public class MyJavaController {
    @Inject
    Connection connection;

    @Inject
    FileSystem fs;

    /* ... */
}

You can then inject existing instances of such classes by using di.jx in Kotlin, or Jx.of(di) in Java.

Example: Injecting an existing instance in Kotlin
val controller = MyJavaController()
di.jx.inject(controller)
Example: Injecting an existing instance in Java
MyJavaController controller = new MyJavaController();
Jx.of(di).inject(controller);

Method injection

Method injection is supported to be compatible with Java injection libraries. It is, however, widely considered as the less semantic injection method.

You can have @Inject annotated method be called at injection.

Example: Java class whose method will be automatically called on injection.
public class MyJavaController {
    @Inject
    public setIO(Connection connection, FileSystem fs) {
        /* ... */
    }
    /* ... */
}

You know the drill, use di.jx in Kotlin or Jx.of(di) in Java the exact same way as for field injection.

Being specific

Qualifiers annotations

javax.inject libraries use the concept of "qualifier annotations", which serves the same purpose as DI’s tag system.

The @Named annotation is a qualifier provided by default, and is supported by default in Kodein-DI-JxInject. In Java, any field or method / constructor parameter annotated with @Named("whatever") will use the String value as tag.

Example: Java class which uses named injection.
public class MyJavaController {
    @Inject @Named("SQL")
    Connection connection; (1)

    @Inject setConnection(@Named("SQL") Connection connection) { /*...*/ } (2)
}

<1>: Field injection. <2>: Method injection.

To inject the connection field, DI will essentially retrieve as di.instance<Connection>(tag = "SQL").

For any other qualifier annotation, you need to provide a function that will transform a qualifier annotation to a tag.

Example: Registering a qualifier annotation
val di = DI {
    import(jxInjectorModule)

    /* Other bindings */

    jxQualifier<MyQualifier> { MyTag(it.value) } (1)
}

<1>: Transforms a MyQualifier qualifier annotation into a MyTag DI tag.

Using erased bindings

If you need to inject erased binding, you can annotate the field or method / constructor parameter with the @ErasedBinding annotation.

Example: Java class which uses erased binding injection.
public class MyJavaController {
    @Inject @ErasedBinding List<Connection> connections;
}

Optional injection

If you need to inject something only if it was bound (and set it to null otherwise), you can use the @OrNull annotation.

Example: Java class which uses nullable injection.
public class MyJavaController {
    @Inject @OrNull Connection connectionOrNull;
}

Provider & Factory injection

You can inject a provider, either by using javax.inject.Provider or kotlin.jvm.functions.Function0. Note that if you are using the latter, you need to use the @ProviderFun annotation.

Example: Java class which uses provider injection.
public class MyJavaController {
    @Inject Provider<Connection> connectionJXProvider;
    @Inject @ProviderFun Function0<Connection> connectionKotlinProvider;
}

To inject a factory, you need to use kotlin.jvm.functions.Function1 annotated with @FactoryFun.

Example: Java class which uses factory injection.
public class MyJavaController {
    @Inject @ProviderFun Function1<String, Connection> connectionFactory;
}