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.

Original source