Джерси + Гризли + 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. Найдите подробную реализацию вышеупомянутого скелета в моем репо. Счастливое Кодирование

Другие вопросы по тегам