Джерси + Гризли + HK2: внедрение зависимости, но не в ресурс
Последующие действия в отношении Джерси + HK2 + Grizzly: правильный способ введения EntityManager?, Я хотел бы понять, как можно использовать внедрение зависимостей в классы, которые не являются ресурсами джерси.
Например, у меня могут быть фоновые задачи, выполняющиеся в ExecutorService, и им может понадобиться EntityManager. Если я попытаюсь @Inject
EntityManager в классе ничего не происходит. Впрыскивая его в @Path
- класс ресурсов джерси, инъекция работает нормально.
Приложение работает как отдельная JVM, а не на сервере приложений Java EE.
Обновление: я создал тестовый сценарий, чтобы продемонстрировать, что я имею в виду. В коде запущен автономный сервер Grizzly с ресурсом Джерси, а также служба ExecutorService. Callable
передается в ExecutorService.
Инъекция EntityManager в ресурс работает, но не в Callable. Там EntityManager остается null
,
Пожалуйста, сообщите, если код лучше хранить здесь, чем на github.
2 ответа
Таким образом, чтобы действительно понять, как работает 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, я настоятельно рекомендую внимательно изучить руководство пользователя. Вы узнаете много нового о том, что происходит с Джерси, а также узнаете о возможностях, которые вы можете включить в приложение на Джерси.
Ниже приведен полный рефакторинг вашего теста. Я не сильно изменился. Любые сделанные мной изменения в значительной степени обсуждались выше.
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, который реализует 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); } }
Элемент списка Теперь пришло время написать свои собственные сервисы и внедрить все необходимые сервисы в соответствующие контроллеры, как показано ниже: package 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. Найдите подробную реализацию вышеупомянутого скелета в моем репо. Счастливое Кодирование