jersey + grizzly + hk2:依赖注入,但不注入资源

跟进Jersey + HK2 + Grizzly:正确的注入方式EntityManager?,我想了解如何在不是球衣资源的类中使用依赖注入.

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.

例如,我可能在 ExecutorService 中运行后台任务,它们可能需要 EntityManager.如果我尝试将 EntityManager @Inject 放入类中,则不会发生任何事情.将其注入到 @Path 注释的球衣资源类中,注入工作正常.

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.

应用程序作为独立的 JVM 运行,而不是在 Java EE 应用程序服务器上.

The application is running as a standalone JVM, not on a Java EE application server.

更新:我创建了一个测试场景来说明我的意思.该代码正在运行一个带有 Jersey 资源的独立 Grizzly 服务器,以及一个 ExecutorService.Callable 被提交给 ExecutorService.

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.

将 EntityManager 注入资源有效,但不能注入 Callable.EntityManager 在那里保持 null.

Injection of the EntityManager into the resource works, but not into the Callable. There the EntityManager remains null.

请告知代码是否保存在这里比保存在 github 上更好.

Please advise if the code is better kept here than on github.

推荐答案

所以要真正了解HK2的工作原理,你应该熟悉它的ServiceLocator.它类似于 Spring ApplicationContext,它是 DI 框架的主要容器.

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.

在独立应用程序中,您可以简单地引导 DI 容器

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());

现在您的 EntityManagerProvider 已注册到容器中.您可以通过以下方式查找 EntityManager

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 {
        ...
}

你实际做的.问题是,BackgroundTask 不是由容器管理的.所以即使在一个独立的引导程序中(如上面的三行代码),实例化任务

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();

就注入而言,什么都不做,因为任务类不由容器管理,并且你自己创建它.如果你想管理它,有几种方法可以将它注册到容器中.您已经发现了一个(使用 AbstractBinder)并将活页夹注册到 ServiceLocator.然后,您无需自己实例化该类,而只需请求它,就像上面的 EntityManager 示例一样.

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);

这样做是导致定位器查找 EntityManager 并将其注入到您的任务中.

What that did was cause the locator to lookup the EntityManager and inject it into your task.

那么这一切如何与泽西岛相适应?Jersey(部分)在运行时处理服务查找和资源注入.这就是它在您的 Jersey 应用程序中起作用的原因.当需要 EntityManager 时,它会查找服务并将其注入到资源实例中.

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.

所以下一个问题是,如果任务在 Jersey 应用程序范围之外运行,你如何注入任务?在大多数情况下,以上所有内容几乎就是它的要点.Jersey 有它自己的 ServiceLocator,尝试获取它的引用并不容易.我们可以给 Jersey 我们的 ServiceLocator,但 Jersey 最终仍然会创建它自己的 定位器,并将用 我们的 定位器填充它.所以最终仍然会有两个定位器.您可以在下面的重构代码中看到我的意思的示例,它检查 ServiceLocatorFeature 中的引用.

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.

但如果您确实想将 ServiceLocator 提供给 Jersey,您可以将其传递给 Grizzly 服务器工厂方法

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
);

现在您仍然可以在泽西岛以外使用您的定位器.不过老实说,在这种情况下,您完全可以不涉及 Jersey,只保留自己的定位器,只需向 Jersey 和您的 ServiceLocator<注册 EntityManagerProvider/代码>.除了额外的代码行之外,我认为它并没有太大的不同.从功能上看,我没有看到任何变化.

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.

要了解有关 HK2 的更多信息,我强烈建议您仔细阅读 用户指南.您将了解很多关于 Jersey 的幕后知识,并了解可以合并到 Jersey 应用程序中的功能.

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));
    }
}

我可能要提到的另一件事是应用程序只需要一个 EntityManagerFactory.创建它的成本很高,并且每次需要 EntityManager 时创建一个并不是一个好主意.查看一种解决方案此处.

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.

相关文章