Micronaut Dependency Injection - The stand-alone Setup

In this post, let's explore how to use Micronaut as the Dependency Injection container in a stand-alone Java application.

Let's start by creating a stand-alone Java application. I make use of the initialisers provided by Gradle (gradle init command). The application structure looks as follows.

➜  microdemo tree
.
├── build.gradle
├── gradle
│   └── wrapper
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── src
    ├── main
    │   ├── java
    │   │   └── com
    │   │       └── nareshak
    │   │           └── demo
    │   │               └── App.java
    │   └── resources
    └── test
        ├── java
        │   └── com
        │       └── nareshak
        │           └── demo
        │               └── AppTest.java
        └── resources

15 directories, 8 files

Let's open the file build.gradle and add the necessary dependencies for Micronaut DI to work. After adding the Micronaut dependencies, build.gradle will be as follows.

plugins {
    id 'java'
    id 'application'
}

repositories {
    jcenter()
}

dependencies {
    annotationProcessor(platform("io.micronaut:micronaut-bom:2.0.2")) // D1
    annotationProcessor("io.micronaut:micronaut-inject-java") // D2
    implementation(platform("io.micronaut:micronaut-bom:2.0.2")) // D3
    implementation("io.micronaut:micronaut-inject") // D4

    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.2'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.6.2'
}

application {
    mainClassName = 'com.nareshak.demo.App'
}

test {
    useJUnitPlatform()
}

I have commented on the newly added dependencies with 'D1' to 'D4'.

D1

D1 adds a BOM dependency in the 'annotationProcessor' scope. This line is responsible for bringing the compatible versions of transitive dependencies into the scope.

D2

D2 brings the dependency micronaut-inject-java along with its transitive dependencies into the  'annotationProcessor' scope. This dependency ensures that the compiler invokes the code from the corresponding jar file before compiling our application code. The dependency tree for  micronaut-inject-java is as follows (obtained through dependencies task).

\--- io.micronaut:micronaut-inject-java -> 2.0.2
     +--- org.slf4j:slf4j-api:1.7.26
     +--- io.micronaut:micronaut-inject:2.0.2
     |    +--- org.slf4j:slf4j-api:1.7.26
     |    +--- javax.annotation:javax.annotation-api:1.3.2
     |    +--- javax.inject:javax.inject:1
     |    +--- io.micronaut:micronaut-core:2.0.2
     |    |    +--- org.slf4j:slf4j-api:1.7.26
     |    |    +--- org.reactivestreams:reactive-streams:1.0.3
     |    |    \--- com.github.spotbugs:spotbugs-annotations:4.0.3
     |    |         \--- com.google.code.findbugs:jsr305:3.0.2
     |    \--- org.yaml:snakeyaml:1.26
     \--- io.micronaut:micronaut-aop:2.0.2
          +--- org.slf4j:slf4j-api:1.7.26
          +--- io.micronaut:micronaut-inject:2.0.2 (*)
          \--- io.micronaut:micronaut-core:2.0.2 (*)

D3

Similar to D1 but this BOM is for the 'implementation' scope.

D4

This line adds 'micronaut-inject' jar and its transitive dependencies to the 'implementation' scope. We will use classes and interfaces from this jar to create and retrieve instances of classes to be injected.

\--- io.micronaut:micronaut-inject -> 2.0.2
     +--- org.slf4j:slf4j-api:1.7.26
     +--- javax.annotation:javax.annotation-api:1.3.2
     +--- javax.inject:javax.inject:1
     +--- io.micronaut:micronaut-core:2.0.2
     |    +--- org.slf4j:slf4j-api:1.7.26
     |    +--- org.reactivestreams:reactive-streams:1.0.3
     |    \--- com.github.spotbugs:spotbugs-annotations:4.0.3
     |         \--- com.google.code.findbugs:jsr305:3.0.2
     \--- org.yaml:snakeyaml:1.26

Let's go ahead and create a class Greeter as follows.

package com.nareshak.demo;

import javax.inject.Singleton;

@Singleton
public class Greeter {
}

We want the class Greeter to be singleton - meaning once instance of the class should exist.

Let's try to get the instance of Greeter through Micronaut as follows.

package com.nareshak.demo;

import io.micronaut.context.ApplicationContext;

public class App {

    public static void main(String[] args) {
        ApplicationContext context = ApplicationContext.run();
        System.out.println(context.getBean(Greeter.class)); // com.nareshak.demo.Greeter@60611244
        System.out.println(context.getBean(Greeter.class)); // com.nareshak.demo.Greeter@60611244
    }
}

The entry point to Micronaut dependency injection in the above code is ApplicationContext. We can get an instance of it by invoking the static method run. Then we invoke the getBean method by passing the class whose instance we need. We observe that we receive the same instance of Greeter on both the invocations of getBean adhering to the singleton property.

Micronaut version used: 2.0.2