javamultithreadinggrizzly

Load and share data-store cache with Grizzly HTTP Server threads


I am creating a Grizzly http server in a Java SE application, pointing to a Resource package with a class that processes the @GET calls from the browser.

By default, Grizzly starts up to 16 threads (0-15) before cycling back around to thread 0 again. Each time these threads are initalised (and, it seems, even when it goes back to thread 0 again), it calls the constructor on the resource class - i.e. the constructor is being called on EVERY GET Request, not just the first one or even just the first 16 (one for each thread).

My constructor creates a Kafka Streams data store, which can't be initialized multiple times (well, I could assign a random identifier each time, but then I'd still get a conflict eventually), so I want to create it once and have it be accessible from all threads.

Is there a simple way to do this in the resource code itself, or am I better off creating the data store elsewhere, and doing some kind of cross-thread call to get the information (in which case, is there any way to inject that reference into the Resource code, e.g. pass it into the constructor?)

Alternatively, can I get Grizzly to keep the objects alive somehow so it doesn't need to keep calling the constructor? What are the pitfalls of that approach, if it's even possible?

Current Code:

Server created and started as follows:

public static HttpServer startServer() 
{
    final ResourceConfig rc = new ResourceConfig().packages("com.project.resource.provider");    
    HttpServer server = GrizzlyHttpServerFactory.createHttpServer(URI.create(BASE_URI), rc);          
    return server;
}

JAX-RS resource:

@Path("/GetFromHere")
public class GetFromHereProvider // in package  com.project.resource.provider
{
    private final ReadOnlyKeyValueStore<String,DetailObject> kafkaDetailObjectStore;
    
    public GetFromHereProvider() throws Exception
    {
        // create kafka streams objects and populate kafkaDetailObjectStore. This is being called on every GET request too, for some reason.
    }

    @GET
    @Path("{id}")
    @Produces(MediaType.TEXT_PLAIN)
    public String getObject(@PathParam("id") String objectID)
    {
        DetailObject obj= kafkaDetailObjectStore(objectID);
        return (obj == null ? "" : obj.toString()); 
    }
}

Solution

  • (The following answer was very helpful in informing this one: https://stackoverflow.com/a/37613409/318414)

    The key to this is to move the constructor logic to a separate service class (terminology?), and then use dependency injection to push a singleton instance of that object into the constructor at runtime.

    The new code looks as follows:

    1. Create the new service class:
    @Singleton
    public ServiceProviderClass
    {
        private final ReadOnlyKeyValueStore<String, DetailObject> kafkaDetailObjectStore;
    
        public ServiceProviderClass()
        {
            // instantiate kafkaDetailObjectStore...
        }
    
        public ReadOnlyKeyValueStore<String, DetailObject> getKafkaDetailObjectStore()
        {
            return kafkaDetailObjectStore;
        }
    }
    
    1. Create a new binder class, which will allow the above service class to be injected. We also tell it to treat it as a Singleton, so that it only creates one instance of the object:
    public class ServiceProviderClassBinder extends AbstractBinder{
        @Override
        protected void configure() {
            bind(ServiceProviderClass.class)
                .to(ServiceProviderClass.class)
                .in(Singleton.class);
    
            // alternatively, bind(new ServiceProviderClass()).to(ServiceProviderClass.class) should also work, and then you don't need the .in call. 
            //If you have Interfaces and Implementations of those Interfaces, the Interface goes in the "to" call, and the concrete implementation in the "bind" call.
            //If you don't specify an instance and you don't specify the .in() call, it defaults to instantiating per-call.
        }
    }
    
    1. Update the GetFromHereProvider class to tell it to inject the service into the constructor
    @Path("/GetFromHere")
    public class GetFromHereProvider // in package  com.project.resource.provider
    {
        private final ServiceProviderClass kafkaDetailObjectStoreServiceProvider;
        
        // CHANGED
        @Inject
        public GetFromHereProvider(ServiceProviderClass kafkaDetailObjectStoreServiceProvider)
        {
            this.kafkaDetailObjectStoreServiceProvider = kafkaDetailObjectStoreServiceProvider;
        }
    
        @GET
        @Path("{id}")
        @Produces(MediaType.TEXT_PLAIN)
        public String getObject(@PathParam("id") String objectID)
        {
            DetailObject obj= kafkaDetailObjectStoreServiceProvider.getKafkaDetailObjectStore()(objectID);
            return (obj == null ? "" : obj.toString()); 
        }
    }
    
    1. Finally, register the binder when you create the ResourceConfig for the Grizzly HTTP Server:
    public static HttpServer startServer() 
    {
        final ResourceConfig rc = new ResourceConfig().packages("com.project.resource.provider");   
        // NEW
        rc.register(new ServiceProviderClassBinder()); 
        HttpServer server = GrizzlyHttpServerFactory.createHttpServer(URI.create(BASE_URI), rc);          
        return server;
    }