This article shows how to use HK2 dependency injection framework in Jersey and enable auto-scanning auto-discovery of the declared @Contract and @Service components.
P.S. Tested with Jersey 3.0.2 and Java 1.8
1. Jersey and HK2 dependency injection
Jersey, by default, uses HK2 (Hundred-Kilobyte Kernel) for dependency injection. And the HK2 is an implementation of JSR-330(Dependency Injection for Java).
For Jersey and HK2 development, we only need to declare jersey-hk2.
pom.xml
<dependency>
<groupId>org.glassfish.jersey.inject</groupId>
<artifactId>jersey-hk2</artifactId>
</dependency>
2. @Contract, @Service, and @Inject
2.1 Review a simple dependency injection in Jersey.
@Contract
public interface MessageService {
String getHello(String name);
}
@Service
public class MessageServiceImpl implements MessageService {
@Override
public String getHello() {
return "Hello World Jersey and HK2"
}
}
@Path("/hello")
public class MyResource {
@Inject
private MessageService msgService;
}
2.2 If we call the /hello endpoint, Jersey will throw the exception “no object available”?
Terminal
org.glassfish.hk2.api.UnsatisfiedDependencyException:
There was no object available in __HK2_Generated_0 for injection at...
Note
For the Jersey and HK2 injection to work correctly, we must register the
@Contractand@Servicemanually or enable the auto-scanning or auto-discovery feature. At this moment, I started to miss the Spring framework auto-scanning feature π
3. HK2 manual register @Contract and @Service
In Jersey and HK2, we can use AbstractBinder to register the @Contract and @Service manually. This AbstractBinder manual registration is suitable for simple injection, and we, developers, control what to declare and inject.
import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
import org.glassfish.jersey.server.ResourceConfig;
// Starts Grizzly HTTP server
public static HttpServer startServer() {
final ResourceConfig config = new ResourceConfig();
config.register(MyResource.class);
config.register(new AbstractBinder(){
@Override
protected void configure() {
// map this service to this contract
bind(MessageServiceImpl.class).to(MessageService.class);
}
});
return GrizzlyHttpServerFactory
.createHttpServer(URI.create(BASE_URI), config);
}
4. HK2 auto scanning @Contract and @Service
To enable Jersey and HK2 auto scanning, we need two things:
- HK2 metadata called
inhabitant files. ServiceLocatorto find the generated inhabitant files and loads the declared @Contract and @Service.
4.1 HK2 inhabitant files
Declares the hk2-metadata-generator dependency on the project classpath, and it will generate the inhabitant files during the compile process and place the output file in META-INF/hk2-locator/default.
pom.xml
<dependency>
<groupId>org.glassfish.hk2</groupId>
<artifactId>hk2-metadata-generator</artifactId>
<version>3.0.2</version>
</dependency>
The below is the auto-generated inhabitant file.
META-INF/hk2-locator/default
#
# Generated by hk2-metadata-generator
#
[com.favtuts.service.MessageServiceImpl]S
contract={com.favtuts.service.MessageService}
Note
Read this HK2 Inhabitant Generators
4.2 ServiceLocator to read the inhabitant files
4.2.1 We can use ServiceLocatorUtilities to get the ServiceLocator and pass it to the Grizzly HTTP Server.
public static HttpServer startServer() {
// scan packages
final ResourceConfig config = new ResourceConfig();
config.register(MyResource.class);
// Get a ServiceLocator
ServiceLocator locator =
ServiceLocatorUtilities.createAndPopulateServiceLocator();
// pass the ServiceLocator to Grizzly Http Server
final HttpServer httpServer =
GrizzlyHttpServerFactory
.createHttpServer(URI.create(BASE_URI), config, locator);
return httpServer;
}
4.2.2 Alternatively, we can create a Feature to enable auto-scanning.
AutoScanFeature.java
package com.favtuts.config;
import jakarta.inject.Inject;
import jakarta.ws.rs.core.Feature;
import jakarta.ws.rs.core.FeatureContext;
import org.glassfish.hk2.api.DynamicConfigurationService;
import org.glassfish.hk2.api.MultiException;
import org.glassfish.hk2.api.Populator;
import org.glassfish.hk2.api.ServiceLocator;
import org.glassfish.hk2.utilities.ClasspathDescriptorFileFinder;
import org.glassfish.hk2.utilities.DuplicatePostProcessor;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
public class AutoScanFeature implements Feature {
@Inject
ServiceLocator serviceLocator;
@Override
public boolean configure(FeatureContext context) {
DynamicConfigurationService dcs =
serviceLocator.getService(DynamicConfigurationService.class);
Populator populator = dcs.getPopulator();
try {
// Populator - populate HK2 service locators from inhabitants files
// ClasspathDescriptorFileFinder - find files from META-INF/hk2-locator/default
populator.populate(
new ClasspathDescriptorFileFinder(this.getClass().getClassLoader()),
new DuplicatePostProcessor());
} catch (IOException | MultiException ex) {
Logger.getLogger(AutoScanFeature.class.getName()).log(Level.SEVERE, null, ex);
}
return true;
}
}
Register the above feature.
public static HttpServer startServer() {
final ResourceConfig config = new ResourceConfig();
config.register(MyResource.class);
// enable auto scan
config.register(AutoScanFeature.class);
return GrizzlyHttpServerFactory
.createHttpServer(URI.create(BASE_URI), config);
}
5. Jersey and HK2, @Service @Named complete example
The below is a complete Jersey and HK2 example to auto scan the @Service and @Contract and @Named (qualifier, multiple implementations for the same interface).
5.1 Project Directory Structure
C:.
β pom.xml
β README.md
ββββsrc
β ββββmain
β ββββjava
β ββββcom
β ββββfavtuts
β β MainApp.java
β β MyResource.java
β β
β ββββconfig
β β AutoScanFeature.java
β β
β ββββservice
β MessageService.java
β MessageServiceAwsImpl.java
β MessageServiceAzureImpl.java
β
ββββtarget
β jersey-hk2-dependency-injection.jar
β β
β ββββMETA-INF
β ββββhk2-locator
β default
5.2 Project Dependencies
pom.xml
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-grizzly2-http</artifactId>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.inject</groupId>
<artifactId>jersey-hk2</artifactId>
</dependency>
<dependency>
<groupId>org.glassfish.hk2</groupId>
<artifactId>hk2-metadata-generator</artifactId>
<version>3.0.2</version>
</dependency>
5.3 @Contract @Service and @Named
We use the @Contract interface and two @Service implementations, and we use @Named to give each implementation a unique name.
MessageService.java
package com.favtuts.service;
import org.jvnet.hk2.annotations.Contract;
@Contract
public interface MessageService {
String getHello();
}
MessageServiceAwsImpl.java
package com.favtuts.service;
import jakarta.inject.Named;
import org.jvnet.hk2.annotations.Service;
@Service @Named("aws")
public class MessageServiceAwsImpl implements MessageService {
@Override
public String getHello() {
return "Hello from AWS";
}
}
MessageServiceAzureImpl.java
package com.favtuts.service;
import jakarta.inject.Named;
import org.jvnet.hk2.annotations.Service;
@Service @Named("azure")
public class MessageServiceAzureImpl implements MessageService {
@Override
public String getHello() {
return "Hello from Azure";
}
}
5.4 Jersey Resources @Path and @Inject
MyResource.java
package com.favtuts;
import com.favtuts.service.MessageService;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
@Path("/hello")
public class MyResource {
@Inject
@Named("aws")
private MessageService awsService;
@Inject
@Named("azure")
private MessageService azureService;
@Path("/hk2/aws")
@GET
@Produces(MediaType.TEXT_PLAIN)
public String helloAws() {
return awsService.getHello();
}
@Path("/hk2/azure")
@GET
@Produces(MediaType.TEXT_PLAIN)
public String helloAzure() {
return azureService.getHello();
}
}
5.5 Jersey application starter
Run the below MainApp.java, and it will start Grizzly HTTP server, auto scan the @Service, and publish the endpoints.
MainApp.java
package com.favtuts;
import com.favtuts.config.AutoScanFeature;
import org.glassfish.grizzly.http.server.HttpServer;
import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
import org.glassfish.jersey.server.ResourceConfig;
import java.net.URI;
import java.util.logging.Level;
import java.util.logging.Logger;
public class MainApp {
public static final String BASE_URI = "http://localhost:8080/";
// Starts Grizzly HTTP server
public static HttpServer startServer() {
final ResourceConfig config = new ResourceConfig();
config.register(MyResource.class);
// enable the auto scanning, see above 4.2.2
config.register(AutoScanFeature.class);
final HttpServer httpServer =
GrizzlyHttpServerFactory
.createHttpServer(URI.create(BASE_URI), config);
return httpServer;
}
public static void main(String[] args) {
try {
final HttpServer httpServer = startServer();
// add jvm shutdown hook
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
try {
System.out.println("Shutting down the application...");
httpServer.shutdownNow();
System.out.println("Done, exit.");
} catch (Exception e) {
Logger.getLogger(MainApp.class.getName()).log(Level.SEVERE, null, e);
}
}));
System.out.println(String.format("Application started.%nStop the application using CTRL+C"));
// block and wait shut down signal, like CTRL+C
Thread.currentThread().join();
} catch (InterruptedException ex) {
Logger.getLogger(MainApp.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
5.6 Test it with cURL
We can use the cURL command to test the /hello endpoints.
Terminal
> curl http://localhost:8080/hello/hk2/azure
Hello from Azure
> curl http://localhost:8080/hello/hk2/aws
Hello from AWS
> curl -i http://localhost:8080/hello/hk2/aws
HTTP/1.1 200 OK
Content-Type: text/plain
Content-Length: 14
Hello from AWS
6. Download Source Code
$ git clone https://github.com/favtuts/java-jax-rs-tutorials.git
$ cd jersey-hk2-dependency-injection
$ mvn clean package
$ java -jar target/jersey-hk2-dependency-injection-1.0.jar