【问题标题】:jersey + grizzly + hk2: Dependency injection, but not into resourcejersey + grizzly + hk2:依赖注入,但不注入资源
【发布时间】:2015-11-03 18:12:46
【问题描述】:

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

例如,我可能在 ExecutorService 中运行后台任务,它们可能需要 EntityManager。如果我尝试 @Inject EntityManager 进入课堂,什么也不会发生。将其注入@Path-annotated jersey 资源类,注入工作正常。

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

更新:我创建了一个test scenario 来说明我的意思。该代码正在运行具有 Jersey 资源以及 ExecutorService 的独立 Grizzly 服务器。将Callable 提交给 ExecutorService。

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

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

【问题讨论】:

    标签: java dependency-injection jersey grizzly hk2


    【解决方案1】:

    所以要真正了解 HK2 的工作原理,你应该熟悉它的ServiceLocator。类似于SpringApplicationContext,是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 应用程序范围之外运行,你如何注入任务?在大多数情况下,以上所有内容几乎就是它的要点。泽西岛有它自己的ServiceLocator,尝试获取它的引用并不容易。我们可以给 Jersey 我们的 ServiceLocator,但 Jersey 最终仍会创建它自己的 定位器,并将用 我们的 定位器填充它。所以最终仍然会有两个定位器。您可以在下面的重构代码中看到我的意思的示例,它检查ServiceLocatorFeature 中的引用。

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

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

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

    要了解有关 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 时创建一个并不是一个好主意。查看一种解决方案here

    【讨论】:

    • 您的详细回复让我很受启发。我找到了另一种让 hk2 了解可注入类的方法:@ServiceHK2 Inhabitant Generator
    • 您能否解释一下如何从外部获取球衣自身定位器的引用?我找不到关于这个的答案,这是我在这里找到的唯一线程。
    • @arisalexis 您可以使用一些技巧,例如使用持有者类,并通过一些静态方法在该类中设置定位器。您可以在 ServiceLocator 可用的任何地方执行此操作,例如在功能中,如上所示,或几乎任何其他地方。 ServiceLocator 可以注入到 Jersey 应用程序的大多数地方。就像它说的那样,这几乎是一个黑客。您还必须考虑在请求范围内有服务的情况。如果您尝试在 Jersey 应用程序之外访问这些服务,您将收到不在请求范围内的错误。单例每个请求对象工作
    • 设置定位器后,只需通过支架访问即可。我从来没有将它用于真正的应用程序,所以我可以提供任何优点和缺点,除了请求范围问题......(在上述评论的末尾,我的意思是“每个查找对象”而不是“每个请求对象” )
    • 我不知道你到底想做什么,但似乎你需要寻找其他类型的集成策略:-(
    【解决方案2】:

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

    请按照以下步骤做同样的 -

    • List item 创建一个名为 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,您需要在此处注册所有服务,如下所示 -

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

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

    【讨论】:

    • 您的帖子缺少关于 DI 进入非球衣管理课程的问题。您的“MainController”是 Jersey 服务。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-11-18
    • 1970-01-01
    • 2013-10-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多