0

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()); 
    }
}
simonalexander2005
  • 4,338
  • 4
  • 48
  • 92

1 Answers1

0

(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;
}
simonalexander2005
  • 4,338
  • 4
  • 48
  • 92