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());
}
}
(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:
@Singleton
public ServiceProviderClass
{
private final ReadOnlyKeyValueStore<String, DetailObject> kafkaDetailObjectStore;
public ServiceProviderClass()
{
// instantiate kafkaDetailObjectStore...
}
public ReadOnlyKeyValueStore<String, DetailObject> getKafkaDetailObjectStore()
{
return kafkaDetailObjectStore;
}
}
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.
}
}
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());
}
}
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;
}