Инъекционная инъекция с Джерси 2.0

Начиная с нуля без какого-либо знания в прошлом году от Джерси 1.x, мне трудно понять, как настроить инъекцию зависимостей в проекте Jersey 2.0.

Я также понимаю, что HK2 доступен в Джерси 2.0, но я не могу найти документы, которые помогают интеграции с Джерси 2.0.

@ManagedBean
@Path("myresource")
public class MyResource {

    @Inject
    MyService myService;

    /**
     * Method handling HTTP GET requests. The returned object will be sent
     * to the client as "text/plain" media type.
     *
     * @return String that will be returned as a text/plain response.
     */
    @GET
    @Produces(MediaType.APPLICATION_JSON)
    @Path("/getit")
    public String getIt() {
        return "Got it {" + myService + "}";
    }
}

@Resource
@ManagedBean
public class MyService {
    void serviceCall() {
        System.out.print("Service calls");
    }
}

pom.xml

<properties>
    <jersey.version>2.0-rc1</jersey.version>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.glassfish.jersey</groupId>
            <artifactId>jersey-bom</artifactId>
            <version>${jersey.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

<dependencies>
    <dependency>
        <groupId>org.glassfish.jersey.core</groupId>
        <artifactId>jersey-common</artifactId>
    </dependency>
    <dependency>
        <groupId>org.glassfish.jersey.core</groupId>
        <artifactId>jersey-server</artifactId>
    </dependency>
    <dependency>
        <groupId>org.glassfish.jersey</groupId>
        <artifactId>jax-rs-ri</artifactId>
    </dependency>
</dependencies>

Я могу заставить контейнер запускать и обслуживать мой ресурс, но как только я добавляю @Inject в MyService, структура генерирует исключение:

SEVERE: Servlet.service() for servlet [com.noip.MyApplication] in context with path [/jaxrs] threw exception [A MultiException has 3 exceptions.  They are:
1. org.glassfish.hk2.api.UnsatisfiedDependencyException: There was no object available for injection at Injectee(requiredType=MyService,parent=MyResource,qualifiers={}),position=-1,optional=false,self=false,unqualified=null,1039471128)
2. java.lang.IllegalArgumentException: While attempting to resolve the dependencies of com.noip.MyResource errors were found
3. java.lang.IllegalStateException: Unable to perform operation: resolve on com.noip.MyResource
] with root cause
org.glassfish.hk2.api.UnsatisfiedDependencyException: There was no object available for injection at Injectee(requiredType=MyService,parent=MyResource,qualifiers={}),position=-1,optional=false,self=false,unqualified=null,1039471128)
    at org.jvnet.hk2.internal.ThreeThirtyResolver.resolve(ThreeThirtyResolver.java:74)


Мой стартовый проект доступен в GitHub: https://github.com/donaldjarmstrong/jaxrs

Ответ 1

Вам нужно определить AbstractBinder и зарегистрировать его в приложении JAX-RS. Связующее определяет, как инъекция зависимости должна создавать ваши классы.

public class MyApplicationBinder extends AbstractBinder {
    @Override
    protected void configure() {
        bind(MyService.class).to(MyService.class);
    }
}

Когда @Inject обнаруживается в параметре или поле типа MyService.class, он создается с использованием класса MyService. Чтобы использовать это связующее, его необходимо зарегистрировать в приложении JAX-RS. В вашем web.xml определите приложение JAX-RS следующим образом:

<servlet>
  <servlet-name>MyApplication</servlet-name>
  <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
  <init-param>
    <param-name>javax.ws.rs.Application</param-name>
    <param-value>com.mypackage.MyApplication</param-value>
  </init-param>
  <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
  <servlet-name>MyApplication</servlet-name>
  <url-pattern>/*</url-pattern>
</servlet-mapping>

Внедрите класс MyApplication (указанный выше в init-param).

public class MyApplication extends ResourceConfig {
    public MyApplication() {
        register(new MyApplicationBinder());
        packages(true, "com.mypackage.rest");
    }
}

В конструкторе класса регистрируется связующее, определяющее инъекцию зависимостей, и мы также сообщаем приложению, где найти ресурсы REST (в вашем случае, MyResource) с помощью вызова метода packages().

Ответ 2

Сначала просто ответьте на комментарий в ответе accepts.

"Что делает привязка? Что делать, если у меня есть интерфейс и реализация?"

Он просто читает bind( implementation ).to( contract ). Вы можете использовать альтернативную цепочку .in( scope ). Объем по умолчанию PerLookup. Поэтому, если вы хотите синглтон, вы можете

bind( implementation ).to( contract ).in( Singleton.class );

Также имеется RequestScoped

Кроме того, вместо bind(Class).to(Class) вы также можете bind(Instance).to(Class), который будет автоматически одинарным.


Добавление в принятый ответ

Для тех, кто пытается выяснить, как зарегистрировать реализацию AbstractBinder в вашем web.xml(т.е. вы не используете ResourceConfig), кажется, что связующее не будет обнаружено через сканирование пакетов, то есть

<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
    <param-name>jersey.config.server.provider.packages</param-name>
    <param-value>
        your.packages.to.scan
    </param-value>
</init-param>

Или это либо

<init-param>
    <param-name>jersey.config.server.provider.classnames</param-name>
    <param-value>
        com.foo.YourBinderImpl
    </param-value>
</init-param>

Чтобы заставить его работать, мне пришлось реализовать Feature:

import javax.ws.rs.core.Feature;
import javax.ws.rs.core.FeatureContext;
import javax.ws.rs.ext.Provider;

@Provider
public class Hk2Feature implements Feature {

    @Override
    public boolean configure(FeatureContext context) {
        context.register(new AppBinder());
        return true;
    }
}

Аннотация @Provider должна позволять отображать Feature при сканировании пакета. Или без сканирования пакетов вы можете явно зарегистрировать Feature в web.xml

<servlet>
    <servlet-name>Jersey Web Application</servlet-name>
    <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
    <init-param>
        <param-name>jersey.config.server.provider.classnames</param-name>
        <param-value>
            com.foo.Hk2Feature
        </param-value>
    </init-param>
    ...
    <load-on-startup>1</load-on-startup>
</servlet>

См. также:

и для общей информации из документации Джерси


UPDATE

Заводы

Помимо основного связывания в принятом ответе, у вас также есть заводы, где вы можете иметь более сложную логику создания, а также иметь доступ к информации контекста запроса. Например

public class MyServiceFactory implements Factory<MyService> {
    @Context
    private HttpHeaders headers;

    @Override
    public MyService provide() {
        return new MyService(headers.getHeaderString("X-Header"));
    }

    @Override
    public void dispose(MyService service) { /* noop */ }
}

register(new AbstractBinder() {
    @Override
    public void configure() {
        bindFactory(MyServiceFactory.class).to(MyService.class)
                .in(RequestScoped.class);
    }
});

Затем вы можете ввести MyService в свой класс ресурсов.

Ответ 3

Выбранный ответ датируется некоторое время назад. Нецелесообразно объявлять каждую привязку в обычном связующем HK2. Я использую Tomcat, и мне просто нужно добавить одну зависимость. Несмотря на то, что он был разработан для Glassfish, он идеально вписывается в другие контейнеры.

   <dependency>
        <groupId>org.glassfish.jersey.containers.glassfish</groupId>
        <artifactId>jersey-gf-cdi</artifactId>
        <version>${jersey.version}</version>
    </dependency>

Убедитесь, что контейнер правильно настроен (см. документацию).

Ответ 4

Поздно, но я надеюсь, что это поможет кому-то.

У меня JAX RS определяется следующим образом:

@Path("/examplepath")
@RequestScoped //this make the diference
public class ExampleResource {

Тогда, в моем коде, наконец, я могу ввести:

@Inject
SomeManagedBean bean;

В моем случае SomeManagedBean является ApplicationScoped bean.

Надеюсь, это поможет кому угодно.

Ответ 5

Oracle рекомендует добавлять аннотацию @Path ко всем типам, которые нужно вводить при объединении JAX-RS с CDI: http://docs.oracle.com/javaee/7/tutorial/jaxrs-advanced004.htm Хотя это далеко не идеально (например, вы получите предупреждение от Джерси при запуске), я решил воспользоваться этим маршрутом, что избавит меня от поддержки всех поддерживаемых типов внутри связующего.

Пример:

@Singleton
@Path("singleton-configuration-service")
public class ConfigurationService {
  .. 
}

@Path("my-path")
class MyProvider {
  @Inject ConfigurationService _configuration;

  @GET
  public Object get() {..}
}

Ответ 6

Если вы предпочитаете использовать Guice и не хотите объявлять все привязки, вы также можете попробовать этот адаптер:

guice-bridge-jit-injector

Ответ 7

Для меня это работает без AbstractBinder если я включаю следующие зависимости в мое веб-приложение (работает на Tomcat 8.5, Jersey 2.27):

<dependency>
    <groupId>javax.ws.rs</groupId>
    <artifactId>javax.ws.rs-api</artifactId>
    <version>2.1</version>
</dependency>
<dependency>
    <groupId>org.glassfish.jersey.containers</groupId>
    <artifactId>jersey-container-servlet</artifactId>
    <version>${jersey-version}</version>
</dependency>
<dependency>
    <groupId>org.glassfish.jersey.ext.cdi</groupId>
    <artifactId>jersey-cdi1x</artifactId>
    <version>${jersey-version}</version>
</dependency>
<dependency>
    <groupId>org.glassfish.jersey.inject</groupId>
    <artifactId>jersey-hk2</artifactId>
    <version>${jersey-version}</version>
</dependency>

Он работает с CDI 1.2/CDI 2.0 для меня (используя Weld 2/3 соответственно).