Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Trouble creating a simple singleton class in Jersey 2 using built-in Jersey dependency injection

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.

like image 504
ricb Avatar asked Apr 25 '14 22:04

ricb


2 Answers

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();
    }
}
like image 141
ricb Avatar answered Nov 11 '22 22:11

ricb


bind(JustOne.class).to(JustOne.class).in(Singleton.class);     

is the most important part. Without "in(Singleton.class)" multiple instances of JustOne will be created.

like image 19
user1739372 Avatar answered Nov 11 '22 20:11

user1739372