9

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

例如,我可能在 ExecutorService 中运行后台任务,它们可能需要 EntityManager。如果我尝试@Inject将 EntityManager 放入类中,则没有任何反应。将其注入@Path-annotated jersey 资源类,注入工作正常。

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

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

将 EntityManager 注入资源有效,但不能注入 Callable。EntityManager 仍然存在null

请告知代码是否更好地保存在 github 上。

4

2 回答 2

11

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

在一个独立的应用程序中,您可以简单地引导 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它不是由容器管理的。所以即使在一个独立的引导程序中(如上面的三行代码),实例化任务

BackgroundTask task = new BackgroundTask();

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

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

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

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

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

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

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

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

现在您仍然可以在泽西岛以外使用您的定位器。不过老实说,在这种情况下,您根本无法让 Jersey 参与进来,而只需保留自己的定位器,只需EntityManagerProvider在 Jersey 和您的ServiceLocator. 除了额外的代码行之外,我认为它并没有太大的不同。从功能上看,我没有看到任何变化。

要了解有关 HK2 的更多信息,我强烈建议您仔细阅读用户指南。您将了解很多关于 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需要时都创建一个并不是一个好主意。在此处查看一种解决方案。

于 2015-08-12T13:50:07.313 回答
0

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

请按照以下步骤执行相同操作 -</p>

  • 列表项创建一个名为 Hk2Feature 的类,它实现了 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;
      }
    }
    
  • 列表项创建一个名为 MyAppBinder 的类,它扩展了 AbstractBinder,您需要在此处注册所有服务,如下所示 -</p>

    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);
      }
    }
    
  • 列表项现在,是时候编写自己的服务并在适当的控制器中注入所有必需的服务,如以下代码 - 包 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”;
        }
    }
    

现在点击 url http://localhost:8080/main?name=Tanuj,你会得到你的结果。这就是在 Grizzly Jersey 应用程序中实现依赖注入的方法。在我的repo中找到上述骨架的详细实现。快乐编码

于 2017-08-02T02:35:21.737 回答