Чтобы понять, как работает 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.
Ваш подробный ответ вдохновил меня. Я нашел другой способ сделать hk2 осведомленным о инъекционных классах: '@ Service' и [HK2 Ink обитатель-генератор] (https://hk2.java.net/2.4.0-b29/inhabitant-generator.html#HK2_Metadata_Generator) , – Hank
Не могли бы вы объяснить, как получить ссылку на собственный локатор джерси извне? Я не могу найти ответ на этот вопрос и его единственную нить, которую я нашел здесь. – arisalexis
@arisalexis Вы можете с помощью взлома использовать класс держателя и установить локатор в этом классе с помощью некоторого статического метода. Вы можете сделать это в любом месте ServiceLocator, например, в Feature, как показано выше, или почти в другом месте. ServiceLocator может быть введен в большинстве мест в приложении Джерси. Как будто это было сказано, это в значительной степени хак. Вы также должны учитывать случаи, когда у вас есть услуги в области запросов. Если вы попытаетесь получить доступ к этим службам за пределами приложения «Джерси», вы получите ошибку не в области запроса. синглтоны для работы каждого объекта запроса –