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

jersey + grizzly + hk2: Dependency injection, but not into resource

跟进 Jersey + HK2 + Grizzly: Proper way to inject EntityManager?,我想了解如何在 class 中使用依赖注入,这些 不是球衣资源

例如,我可能在 ExecutorService 中有后台任务 运行,它们可能需要 EntityManager。如果我尝试将 EntityManager @Inject 放入 class,什么也不会发生。将其注入 @Path-注释球衣资源 class,注入工作正常。

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

更新: 我创建了一个 test scenario 来证明我的意思。代码是 运行 带有 Jersey 资源的独立 Grizzly 服务器,以及 ExecutorService。一个Callable被提交给ExecutorService。

可以将 EntityManager 注入到资源中,但不能注入到 Callable 中。 EntityManager 仍然存在 null.

如果代码保存在这里比 github 更好,请告知。

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

在独立应用程序中,您可以 bootstrap DI 容器,只需执行

ServiceLocatorFactory locatorFactory = ServiceLocatorFactory.getInstance();
ServiceLocator serviceLocator = locatorFactory.create("TestLocator");
ServiceLocatorUtilities.bind(serviceLocator, new EntityManagerProvider());

现在您的 EntityManagerProvider 已注册到容器中。您可以通过

简单地查找 EntityManager
EntityManager em = serviceLocator.getService(EntityManager.class);

现在为了能够利用容器注入,服务需要由容器管理。例如说你有这个

public class BackgroundTask implements Callable<String> {

    @Inject
    EntityManager em;

    @Override
    public String call() throws Exception {
        ...
}

你实际上是这样做的。问题是,BackgroundTask 不是由容器管理的。所以即使在独立的bootstrap中(如上面的三行代码),实例化任务

BackgroundTask task = new BackgroundTask();

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

或者您可以简单地显式注入任务,即

BackgroundTask task = new BackgroundTask(); 
serviceLocator.inject(task);

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

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

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

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

server = GrizzlyHttpServerFactory.createHttpServer(
        URI.create(BASE_URI),
        config, 
        serviceLocator
);

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

要了解有关 HK2 的更多信息,我强烈建议您彻底阅读 user guide。您将学到很多关于 Jersey 幕后发生的事情,并了解可以合并到 Jersey 应用程序中的功能。

以下是测试的完整重构。我真的没有太大改变。我所做的任何更改都在上面讨论过。

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 时都创建一个不是一个好主意。查看一种解决方案 .

声明:使用Grizzly和Jersey实现依赖注入

请按照以下步骤进行相同操作 –

  • 列表项创建一个名为 Hk2Feature 的 class 实现功能 -

    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 创建一个名为 MyAppBinder 的 class 它扩展了 AbstractBinder,你需要在这里注册所有服务,如下所示 –

    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 现在,是时候编写您自己的服务并将所有必需的服务注入您适当的控制器中,如下面的代码 – 包裹 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”;
        }
    }
    

现在点击urlhttp://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。快乐编码