Inyección de dependencia con Jersey 2.0


A partir de cero sin ningún Maillot anterior 1.x conocimiento, estoy teniendo dificultades para entender cómo configurar la inyección de dependencias en mi proyecto Jersey 2.0.

También entiendo que HK2 está disponible en Jersey 2.0, pero parece que no puedo encontrar documentos que ayuden con la integración de Jersey 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>

Puedo hacer que el contenedor se inicie y sirva mi recurso, pero tan pronto como agrego @Inject a MyService, el framework lanza un excepción:

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)


Mi proyecto inicial está disponible en GitHub: https://github.com/donaldjarmstrong/jaxrs

Author: Paul Samsotha, 2013-04-25

6 answers

Necesita definir un AbstractBinder y registrarlo en su aplicación JAX-RS. El binder especifica cómo la inyección de dependencias debe crear sus clases.

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

Cuando se detecta @Inject en un parámetro o campo de tipo MyService.class se crea una instancia utilizando la clase MyService. Para utilizar este aglutinante, debe estar registrado con la aplicación JAX-RS. En su web.xml, defina una aplicación JAX-RS como esta:

<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>

Implementar la clase MyApplication (especificado anteriormente en el init-param).

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

El binder que especifica la inyección de dependencias se registra en el constructor de la clase, y también le indicamos a la aplicación dónde encontrar los recursos REST (en su caso, MyResource) usando la llamada al método packages().

 91
Author: joscarsson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-11-05 18:13:05

Primero solo para responder un comentario en la respuesta acepta.

"¿Qué hace bind? ¿Qué pasa si tengo una interfaz y una implementación?"

Simplemente se lee bind( implementation ).to( contract ). Puede alternar la cadena .in( scope ). Ámbito predeterminado de PerLookup. Así que si quieres un singleton, puedes

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

También Hay un RequestScoped disponible

También, en lugar de bind(Class).to(Class), también puede bind(Instance).to(Class), que será automáticamente un singleton.


Añadiendo a la aceptada respuesta

Para aquellos que intentan averiguar cómo registrar su implementación AbstractBinder en su web.xml (es decir, no está utilizando un ResourceConfig), parece que el binder no se descubrirá a través del escaneo de paquetes, es decir,

<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>

O bien esto

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

Para que funcione, tuve que implementar un 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;
    }
}

La anotación @Provider debería permitir que el Feature sea recogido por el escaneo del paquete. O sin escaneo de paquetes, puede explícitamente registrar el Feature en el 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>

Véase También:

Y para información general de la Jersey documentación


ACTUALIZAR

Fábricas

Aparte del enlace básico en la respuesta aceptada, también tiene factories, donde puede tener una lógica de creación más compleja, y también tener acceso a solicitar información de contexto. Por ejemplo

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);
    }
});

Luego puede inyectar MyService en su clase de recurso.

 44
Author: Paul Samsotha,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-09-23 02:29:43

La respuesta seleccionada data de hace un tiempo. No es práctico declarar cada encuadernación en una carpeta personalizada HK2. Estoy usando Tomcat y solo tuve que agregar una dependencia. A pesar de que fue diseñado para Glassfish cabe perfectamente en otros contenedores.

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

Asegúrese de que su contenedor también esté configurado correctamente ( consulte la documentación).

 11
Author: otonglet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-01-08 19:49:19

Tarde, pero espero que esto ayude a alguien.

Tengo mi JAX RS definido así:

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

Entonces, en mi código finalmente puedo inyectar:

@Inject
SomeManagedBean bean;

En mi caso, el SomeManagedBean es un frijol con aplicación.

Espero que esto ayude a alguien.

 5
Author: gjijon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-10-06 07:51:52

Oracle recomienda agregar la anotación @Path a todos los tipos a inyectar al combinar JAX-RS con CDI: http://docs.oracle.com/javaee/7/tutorial/jaxrs-advanced004.htm Aunque esto está lejos de ser perfecto (por ejemplo, recibirá una advertencia de Jersey en el inicio), decidí tomar esta ruta, lo que me ahorra mantener todos los tipos compatibles dentro de una carpeta.

Ejemplo:

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

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

  @GET
  public Object get() {..}
}
 2
Author: Benjamin Mesing,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-08-24 09:28:18

Si prefiere usar Guice y no desea declarar todos los enlaces, también puede probar este adaptador:

Guice-bridge-jit-injector

 0
Author: Choi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-07-03 20:18:36