HK2 is an implementation of JSR-330 in a JavaSE environment.
JSR-330 defines services and injection points that can be dynamically discovered at runtime and which allow for Inversion of Control (IoC) and dependency injection (DI).

Introduction

Most developers are familiar with using the Spring Container for dependency injection, in this post we are going to look at dependency injection using HK2.

Prerequisites

To follow along with this post, your development environment must satisfy the following requirements:

Project Structure

At the end of this post, you should have a project structure similar to the one illustrated below:

.
|__src/
|  |__main/
|  |  |__java/
|  |  |  |__com/
|  |  |  |  |__juliuskrah/
|  |  |  |  |  |__gs/
|  |  |  |  |  |  |__Application.java
|  |  |  |  |  |  |__MessagePrinter.java
|  |  |  |  |  |  |__MessageService.java
|  |  |  |  |  |  |__MessageServiceImpl.java
|__pom.xml

You can also find the complete sample in the github repository.

Project Dependencies

To get started, we need to gather our project dependencies. This is a maven based project, and we can easily declare our dependencies in a pom.xml file.

file: pom.xml

...
<properties>
  <maven.compiler.source>1.8</maven.compiler.source>
  <maven.compiler.target>1.8</maven.compiler.target>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  <hk2.version>2.5.0-b43</hk2.version>
</properties>

<dependencies>
  <dependency>
    <groupId>org.glassfish.hk2</groupId>
    <artifactId>hk2-locator</artifactId>
    <version>${hk2.version}</version>
  </dependency>
  <dependency>
    <groupId>org.glassfish.hk2</groupId>
    <artifactId>hk2-metadata-generator</artifactId>
    <version>${hk2.version}</version>
    <scope>provided</scope>
  </dependency>
</dependencies>

We have declared two dependencies, hk2-locator and hk2-metadata-generator. The first dependency contains the hk2 library and its transitive dependencies. We will talk about the second dependency later in this post.

HK2 Services and Injection

To demonstrate dependency injection using HK2, we will create a concrete Service class MessagePrinter and inject a Contract interface MessageService. The MessagePrinter will print messages delivered via the MessageService:

file: src/main/java/com/juliuskrah/gs/MessageService.java

@Contract
@FunctionalInterface
public interface MessageService {
  String getMessage();
}

The MessageService interface defines one contract getMessage which returns a String. The MessagePrinter will inject an instance of MessageService and print its messages:

file: src/main/java/com/juliuskrah/gs/MessagePrinter.java

1
2
3
4
5
6
7
8
9
10
11
12
13
@Service
public class MessagePrinter {
  private MessageService messageService;

  @Inject
  public MessagePrinter(MessageService messageService) {
    this.messageService = messageService;
  }

  public void printMessage() {
    System.out.println(this.messageService.getMessage());
  }
}

On line 5 we use constructor injection to inject the MessageService instance.

Next we create a concrete service implementation of MessageService. It is this service implementation that HK2 will inject into the MessageService contract.

file: src/main/java/com/juliuskrah/gs/MessageServiceImpl.java

@Service
public class MessageServiceImpl implements MessageService {
    
    @Override
    public String getMessage() {
        return "Hello World!";
    }
}

Now that we have our services and contract set up, we wire it all together using HK2.
In order for HK2 to automatically find services at runtime it can read files called inhabitant files. These are usually placed in your JAR file at location META-INF/hk2-locator. Normally the file is named default. (You can however use a different file name or location(s) by using more specific API). HK2 has a tool for automatically creating these files based on class files annotated with @Service. There is also a simple API for creating and populating a ServiceLocator with services found in these files. This is where the second dependency we declared earlier comes into play.
The HK2 Metadata Generator will generate hk2 inhabitant files during the compilation of your java files. It is a JSR-269 annotation processor that handles the @Service annotation. The only requirement for using it is to put the javax.inject, hk2-utils, hk2-api and hk2-metadata-generator libraries in the classpath of the javac process.

In this post we use the HK2 Metadata Generator in a Maven based build system:

<dependency>
  <groupId>org.glassfish.hk2</groupId>
  <artifactId>hk2-metadata-generator</artifactId>
</dependency>

Since Maven uses transitive dependencies this is all you need to add as a dependency during build.

In order to have our program automatically load the files generated with the hk2-inhabitant-generator you can use the createAndPopulateServiceLocator method near the start of our main method, like this:

file: src/main/java/com/juliuskrah/gs/Application.java

public class Application {

  public static void main(String... args) {
    ServiceLocator locator = ServiceLocatorUtilities.createAndPopulateServiceLocator();
    MessagePrinter messagePrinter = locator.getService(MessagePrinter.class);

    messagePrinter.printMessage();
  }
}

Just like that, we are done

Conclusion

In this post we learned how to implement Dependency Injection using HK2. We demonstrated our example using services and contracts and constructor based injection.
You can find the source to this guide in the github repository. Until the next post, keep doing cool things :smile:.