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
@Contract
and@Service
manually 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
. ServiceLocator
to 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