2015-08-11 6 views
4

Следующее за Jersey + HK2 + Grizzly: Proper way to inject EntityManager?, я хотел бы понять, как можно использовать инъекцию зависимостей в классах, которые являются не носителями ресурсов.джерси + гризли + hk2: инъекция зависимости, но не в ресурс

В качестве примера у меня могут быть фоновые задачи, выполняемые в ExecutorService, и им может понадобиться EntityManager. Если я попытаюсь установить @Inject EntityManager в класс, ничего не произойдет. Внедрение его в класс ресурсов заданного ресурса @Path, работа с инъекциями прекрасна.

Приложение работает как автономный JVM, а не на сервере приложений Java EE.

Обновление: Я создал test scenario, чтобы продемонстрировать, что я имею в виду. В коде работает автономный сервер Grizzly с ресурсом Jersey, а также ExecutorService. A Callable представляется в Исполнительный орган.

Инъекция EntityManager в ресурс работает, но не в Callable. Там EntityManager остается null.

Пожалуйста, сообщите, если код лучше хранится здесь, чем на github.

ответ

7

Чтобы понять, как работает HK2, вы должны ознакомиться с его ServiceLocator. Он аналогичен весне 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 и ввести его в вашу задачу.

Так как же все это вписывается в Джерси?Джерси (частично) обрабатывает поиск сервисов и впрыскивание ресурсов во время работы. Вот почему это работает в вашем заявлении на Джерси. Когда требуется EntityManager, он ищет службу, которая вводит ее в экземпляр ресурса.

Итак, следующий вопрос: если задачи выполняются за пределами приложения приложения Джерси, как вы можете ввести задачу? По большей части все вышеизложенное в значительной степени является его сутью. Джерси имеет свой собственный ServiceLocator, и нелегко попытаться получить ссылку на него. Мы могли бы дать Джерси ServiceLocator, но Джерси в конечном счете все еще создает собственный локатор и заселят его нашим локатором. Таким образом, в конечном итоге все еще будут два локатора. Вы можете увидеть пример того, что я имею в виду в обновленном коде ниже, где он проверяет ссылки в ServiceLocatorFeature.

Но если вы хотите, чтобы обеспечить ServiceLocator в Джерси, вы можете передать его на сервер Grizzly фабричный метод

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

Теперь вы можете использовать локатор за пределами Джерси. Честно говоря, в этом случае вы могли бы не включать Джерси вообще и просто сохранить свой локатор, и просто зарегистрировать EntityManagerProvider с Джерси и вашим ServiceLocator. Я не вижу, чтобы это действительно имело большое значение, за исключением дополнительной строки кода. Функционально я не вижу никаких изменений.

Чтобы узнать больше о HK2, я настоятельно рекомендую тщательно пройти через user guide. Вы узнаете много о том, что происходит под капотом с Джерси, а также узнайте об особенностях, которые вы можете включить в приложение Джерси.

Ниже приведен полный рефакторинг вашего теста. Я не сильно изменился. Любые изменения, которые я сделал, в значительной степени обсуждены выше.

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.

+1

Ваш подробный ответ вдохновил меня. Я нашел другой способ сделать hk2 осведомленным о инъекционных классах: '@ Service' и [HK2 Ink обитатель-генератор] (https://hk2.java.net/2.4.0-b29/inhabitant-generator.html#HK2_Metadata_Generator) , – Hank

+0

Не могли бы вы объяснить, как получить ссылку на собственный локатор джерси извне? Я не могу найти ответ на этот вопрос и его единственную нить, которую я нашел здесь. – arisalexis

+0

@arisalexis Вы можете с помощью взлома использовать класс держателя и установить локатор в этом классе с помощью некоторого статического метода. Вы можете сделать это в любом месте ServiceLocator, например, в Feature, как показано выше, или почти в другом месте. ServiceLocator может быть введен в большинстве мест в приложении Джерси. Как будто это было сказано, это в значительной степени хак. Вы также должны учитывать случаи, когда у вас есть услуги в области запросов. Если вы попытаетесь получить доступ к этим службам за пределами приложения «Джерси», вы получите ошибку не в области запроса. синглтоны для работы каждого объекта запроса –

0

Заявление: Реализация Dependency Injection с использованием Grizzly и Джерси

Выполните следующие шаги, чтобы сделать то же самое -

  • Элемент списка Создайте класс 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); 
        } 
    } 
    
  • Элемент списка Теперь пришло время, чтобы написать свои собственные услуги и ввести все необходимые услуги в своих соответствующих контроллеров, как показано ниже код - пакет ком. образец.ди;

    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. Happy Coding

+0

пропустил вопрос, касающийся DI, в классы, не связанные с Джерси. Ваш «MainController» - это служба Джерси. – Hank

Смежные вопросы