Following up on Jersey + HK2 + Grizzly: Proper way to inject EntityManager?, I would like to understand how it is possible use dependency injection in classes which are not jersey resources.
As an example, I might have background tasks running in an ExecutorService, and they might need an EntityManager. If I attempt to @Inject
the EntityManager into the class, nothing happens. Injecting it into a @Path
-annotated jersey resource class, injecting works fine.
The application is running as a standalone JVM, not on a Java EE application server.
Update: I have created a test scenario to demonstrate what I mean. The code is running a standalone Grizzly server with a Jersey resource, as well as an ExecutorService. A Callable
is submitted to the ExecutorService.
Injection of the EntityManager into the resource works, but not into the Callable. There the EntityManager remains null
.
Please advise if the code is better kept here than on github.
So to really understand how HK2 works, you should become familiar with its ServiceLocator
. It is analogous to Spring ApplicationContext
, which is the main container for the DI framework.
In a standalone app, you could bootstrap the DI container simply by doing
ServiceLocatorFactory locatorFactory = ServiceLocatorFactory.getInstance();
ServiceLocator serviceLocator = locatorFactory.create("TestLocator");
ServiceLocatorUtilities.bind(serviceLocator, new EntityManagerProvider());
Now your EntityManagerProvider
is registered into the container. You can lookup the EntityManager
simply by doing
EntityManager em = serviceLocator.getService(EntityManager.class);
Now in order to be able to take advantage of injection by the container, the service needs to be managed by the container. For example say you have this
public class BackgroundTask implements Callable<String> {
@Inject
EntityManager em;
@Override
public String call() throws Exception {
...
}
which you actually do. The problem is, the BackgroundTask
is not managed by the container. So even in a standalone bootstrap (like the three lines of code above), instantiating the task
BackgroundTask task = new BackgroundTask();
does nothing, as far as injection, as the task class is not managed by the container, and you are creating it yourself. If you wanted it managed, there a few ways to register it to the container. You've discovered one already (use an AbstractBinder
) and register the binder to the ServiceLocator
. Then instead of instantiating the class yourself, you just request it, like the EntityManager
example above.
Or you can simply explicitly inject the task, i.e
BackgroundTask task = new BackgroundTask();
serviceLocator.inject(task);
What that did was cause the locator to lookup the EntityManager
and inject it into your task.
So how does this all fit in with Jersey? Jersey (partly) handles lookup of services and injection into resources during it's runtime. That's why it work's in your Jersey application. When the EntityManager
is needed, it looks up the service an injects it into the resource instance.
So the next question is, if the tasks are being run outside the scope the Jersey application, how can you inject the task? For the most part, all the above is pretty much the gist of it. Jersey has it's own ServiceLocator
, and it's not easy to try a obtain a reference to it. We could give Jersey our ServiceLocator
, but Jersey ultimately still creates it's own locator and will populate it with our locator. So ultimately there would still be two locators. You can see an example of what I mean in the refactored code below, where it check the references in the ServiceLocatorFeature
.
But if you do want to provide the ServiceLocator
to Jersey, you can pass it to the Grizzly server factory method
server = GrizzlyHttpServerFactory.createHttpServer(
URI.create(BASE_URI),
config,
serviceLocator
);
Now you can still use your locator outside of Jersey. Honestly though, in this case, you could not involve Jersey at all and just keep your own locator, and just register the EntityManagerProvider
with both Jersey and your ServiceLocator
. I don't see it really making much difference, except for the extra line of code. Functionally, I don't see any change.
To learn more about HK2, I highly recommend thoroughly going through the user guide. You'll learn a lot about what goes on under the hood with Jersey, and also learn about features that you can incorporate into a Jersey application.
Below is the complete refactor of your test. I didn't really change much. Any changes I made are pretty much discussed above.
public class DependencyInjectionTest {
private final ServiceLocatorFactory locatorFactory
= ServiceLocatorFactory.getInstance();
private ServiceLocator serviceLocator;
private final static String BASE_URI = "http://localhost:8888/";
private final static String OK = "OK";
private HttpServer server;
private ExecutorService backgroundService;
public class EntityManagerProvider extends AbstractBinder
implements Factory<EntityManager> {
private final EntityManagerFactory emf;
public EntityManagerProvider() {
emf = Persistence.createEntityManagerFactory("derbypu");
}
@Override
protected void configure() {
bindFactory(this).to(EntityManager.class);
System.out.println("EntityManager binding done");
}
@Override
public EntityManager provide() {
EntityManager em = emf.createEntityManager();
System.out.println("New EntityManager created");
return em;
}
@Override
public void dispose(EntityManager em) {
em.close();
}
}
public class BackgroundTask implements Callable<String> {
@Inject
EntityManager em;
@Override
public String call() throws Exception {
System.out.println("Background task started");
Assert.assertNotNull(em); // will throw exception
System.out.println("EntityManager is not null");
return OK;
}
}
public class ServiceLocatorFeature implements Feature {
@Override
public boolean configure(FeatureContext context) {
ServiceLocator jerseyLocator
= org.glassfish.jersey.ServiceLocatorProvider
.getServiceLocator(context);
System.out.println("ServiceLocators are the same: "
+ (jerseyLocator == serviceLocator));
return true;
}
}
@Path("/test")
public static class JerseyResource {
@Inject
EntityManager em;
@GET
@Produces(MediaType.TEXT_PLAIN)
public Response doGet() {
System.out.println("GET request received");
Assert.assertNotNull(em);
System.out.println("EntityManager is not null");
return Response.ok()
.entity(OK)
.build();
}
}
@Before
public void setUp() {
serviceLocator = locatorFactory.create("TestLocator");
ServiceLocatorUtilities.bind(serviceLocator, new EntityManagerProvider());
System.out.println("Setting up");
ResourceConfig config = new ResourceConfig();
config.register(new ServiceLocatorFeature());
//config.register(new EntityManagerProvider());
config.register(JerseyResource.class);
// can't find a better way to register the resource
//config.registerInstances(JerseyResource.class);
server = GrizzlyHttpServerFactory.createHttpServer(
URI.create(BASE_URI),
config, serviceLocator
);
backgroundService = Executors.newSingleThreadScheduledExecutor();
}
@After
public void tearDown() {
System.out.println("Shutting down");
server.shutdownNow();
backgroundService.shutdownNow();
}
@Test
public void testScheduledBackgroundTask() throws Exception {
Assert.assertTrue(server.isStarted());
BackgroundTask task = new BackgroundTask();
serviceLocator.inject(task);
Future<String> f = backgroundService.submit(task);
System.out.println("Background task submitted");
try {
Assert.assertEquals(OK, f.get()); // forces Exception
} catch (ExecutionException | InterruptedException ex) {
System.out.println("Caught exception " + ex.getMessage());
ex.printStackTrace();
Assert.fail();
}
}
@Test
public void testBackgroundTask() throws Exception {
Assert.assertTrue(server.isStarted());
BackgroundTask task = new BackgroundTask();
serviceLocator.inject(task);
System.out.println("Background task instantiated");
Assert.assertEquals(OK, task.call());
}
@Test
public void testResource() {
Assert.assertTrue(server.isStarted());
Client client = ClientBuilder.newClient();
WebTarget target = client.target(BASE_URI);
Response r = target.path("test")
.request()
.get();
Assert.assertEquals(200, r.getStatus());
Assert.assertEquals(OK, r.readEntity(String.class));
}
}
Another thing I might mention is that you should need only one EntityManagerFactory
for the application. It's expensive to create, and creating one every time the EntityManager
is needed is not a good idea. See one solution here.
Statement: Implementation of Dependency Injection using Grizzly and Jersey
Please follow the below steps to do the same –
List item Create a class called Hk2Feature which implements Feature -
package com.sample.di;
import javax.ws.rs.core.Feature;
import javax.ws.rs.core.FeatureContext;
import javax.ws.rs.ext.Provider;
@Provider
public class Hk2Feature implements Feature {
public boolean configure(FeatureContext context) {
context.register(new MyAppBinder());
return true;
}
}
List item Create a class called MyAppBinder which extends AbstractBinder and you need to register all the services here like below –
package com.sample.di;
import org.glassfish.hk2.utilities.binding.AbstractBinder;
public class MyAppBinder extends AbstractBinder {
@Override
protected void configure() {
bind(MainService.class).to(MainService.class);
}
}
List item Now, it’s time to write your own services and inject all the required services in your appropriate controllers like below code –
package com.sample.di;
public class MainService {
public String testService(String name) {
return “Hi” + name + “..Testing Dependency Injection using Grizlly Jersey “;
}
}
package com.sample.di;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
@Path(“/main”)
public class MainController {
@Inject
public MainService mainService;
@GET
public String get(@QueryParam(“name”) String name) {
return mainService.testService(name);
}
@GET
@Path(“/test”)
@Produces(MediaType.APPLICATION_JSON)
public String ping() {
return “OK”;
}
}
Now hit the url http://localhost:8080/main?name=Tanuj and you will get your result. This is how you can achieve dependency injection in Grizzly Jersey application. Find the detailed implementation of the above skeleton in my repo. Happy Coding