Trouble creating a simple singleton class in Jersey 2 using built-in Jersey dependency injection
dependency-injection, hk2, jax-rs, jersey-2.0, singleton
Solution
Okay, after spending a very healthy chunk of time on this, I got it working.
I used an HK2 AbstractBinder and JAX-RS Feature (javax.ws.rs.core.Feature).
It turned out that no Provider/ContextResolver was required.
Application:
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
import java.util.HashSet;
import java.util.Set;
@ApplicationPath("/*")
public class MainApplication extends Application {
@Override
public Set<Class<?>> getClasses() {
Set<Class<?>> classes = new HashSet<>();
classes.add(TestResource.class);
classes.add(JustOneFeature.class);
return classes;
}
}
To-be-singleton class:
public class JustOne {
private int secretNumber = 0;
public int getSecretNumber() {
return secretNumber;
}
public void bumpSecretNumber() {
secretNumber += 1;
}
}
Binder:
import org.glassfish.hk2.utilities.binding.AbstractBinder;
public class JustOneBinder extends AbstractBinder {
@Override
protected void configure() {
bind(new JustOne()).to(JustOne.class);
}
}
Feature:
import javax.ws.rs.core.Feature;
import javax.ws.rs.core.FeatureContext;
public class JustOneFeature implements Feature {
@Override
public boolean configure(final FeatureContext context) {
context.register(new JustOneBinder());
return true;
}
}
Resource that gets singleton injected:
import javax.inject.Inject;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
@Path("/test")
public class TestResource {
@Inject
private JustOne justOne;
@GET
@Consumes(MediaType.TEXT_PLAIN)
@Produces(MediaType.APPLICATION_JSON)
@Path("/func1/{input}")
public Response func1(@PathParam("input") int input) {
justOne.bumpSecretNumber();
String responseData = String.format("{ \"result\": %s }", input + justOne.getSecretNumber());
return Response.ok(responseData).build();
}
}
Problem
I am having trouble getting a very basic implementation of a singleton class off the ground with Jersey 2 (2.7) and only Jersey's built-in HK2 dependency injection. I am running this on Tomcat. My goal is to create a singleton instance of a support class that will be used by various web service methods. I don't have a strong preference between constructor injection, method injection, and annotating a class member (as I do below). Here is my to-be-singleton class: ``` package singletest; import javax.inject.Singleton; @Singleton public class JustOne { private int secretNumber = 0; public void hitMe(int input) { secretNumber += input; } @Override public String toString() { return String.format("{ \"secretNumber\": %s }", secretNumber); } } ``` Here is my application class: ``` package singletest; import javax.ws.rs.ApplicationPath; import javax.ws.rs.core.Application; import java.util.HashSet; import java.util.Set; @ApplicationPath("/*") public class MainApplication extends Application { @Override public Set<Class<?>> getClasses() { Set<Class<?>> classes = new HashSet<>(); classes.add(TestResource.class); return classes; } @Override public Set<Object> getSingletons() { Set<Object> singletons = new HashSet<>(); singletons.add(new JustOneProvider()); return singletons; } } ``` Here is my Provider/ContextResolver class: ``` package singletest; import javax.ws.rs.ext.ContextResolver; import javax.ws.rs.ext.Provider; @Provider public class JustOneProvider implements ContextResolver<JustOne> { private static JustOne justOne = new JustOne(); @Override public JustOne getContext(Class<?> type) { return justOne; } } ``` web.xml: ``` <?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <filter> <filter-name>singletest.MainApplication</filter-name> <filter-class>org.glassfish.jersey.servlet.ServletContainer</filter-class> <init-param> <param-name>javax.ws.rs.Application</param-name> <param-value>singletest.MainApplication</param-value> </init-param> <!-- pass to next filter if Jersey/App returns 404 --> <init-param> <param-name>jersey.config.servlet.filter.forwardOn404</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>singletest.MainApplication</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app> ``` The resource where I intend to inject the singleton instance of my JustOne class: ``` package singletest; import javax.inject.Inject; import javax.ws.rs.*; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; @Path("/test") public class TestResource { @Inject private JustOne justOne; @GET @Consumes(MediaType.TEXT_PLAIN) @Produces(MediaType.APPLICATION_JSON) @Path("/func1/{input}") public Response func1(@PathParam("input") int input) { justOne.hitMe(input); String responseData = justOne.toString(); return Response.ok(responseData).build(); } } ``` This warning is raised when the war is deployed/initialized in Jersey: ``` Apr 28, 2014 11:48:25 AM org.glassfish.jersey.server.ApplicationHandler initialize INFO: Initiating Jersey application, version Jersey: 2.7 ${buildNumber}... Apr 28, 2014 11:48:25 AM org.glassfish.jersey.internal.inject.Providers checkProviderRuntime WARNING: A provider java.lang.Class registered in SERVER runtime does not implement any provider interfaces applicable in the SERVER runtime. Due to constraint configuration problems the provider java.lang.Class will be ignored. ``` And the error I get when I call this web service: ``` type Exception report message A MultiException has 3 exceptions. They are: description The server encountered an internal error that prevented it from fulfilling this request. exception javax.servlet.ServletException: A MultiException has 3 exceptions. They are: 1. org.glassfish.hk2.api.UnsatisfiedDependencyException: There was no object available for injection at Injectee(requiredType=JustOne,parent=TestResource,qualifiers={}),position=-1,optional=false,self=false,unqualified=null,58952407) 2. java.lang.IllegalArgumentException: While attempting to resolve the dependencies of singletest.TestResource errors were found 3. java.lang.IllegalStateException: Unable to perform operation: resolve on singletest.TestResource org.glassfish.jersey.servlet.WebComponent.service(WebComponent.java:333) org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:372) org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:335) org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:218) org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52) ``` So, I get it: hk2 isn't getting enough information to create/bind a JustOne instance and/or find/use my JustOneProvider. I'm sure I'm missing something basic. Any guidance would be appreciated.