Inyección de dependencia con Jersey 2.0

108

Comenzando desde cero sin ningún conocimiento previo de Jersey 1.x, me cuesta entender cómo configurar la inyección de dependencia en mi proyecto de 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 marco arroja una 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

donnie_armstrong
fuente

Respuestas:

107

Necesita definir un AbstractBindery registrarlo en su aplicación JAX-RS. El enlazador especifica cómo la inyección de dependencia debe crear sus clases.

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

Cuando @Injectse detecta en un parámetro o campo de tipo MyService.class, se crea una instancia mediante la clase MyService. Para utilizar este archivador, 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>

Implemente la MyApplicationclase (especificada arriba en init-param).

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

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

Joscarsson
fuente
1
¿Qué pasa con EntityManager? ¿Alguna pista sobre cómo vincularlo, para que pueda inyectarlo a través de @PersistenceContext?
Johannes Staehlin
4
No estoy seguro de qué EntityManageres, pero a juzgar por docs.oracle.com/javaee/6/api/javax/persistence/… parece ser una interfaz. Puede vincularlo usando bind(EntityManagerImpl.class).to(EntityManager.class)(que vinculará una clase que EntityManagerImplimplementa la interfaz EntityManager. Si necesita usar una fábrica, eche un vistazo bindFactory()en el AbstractBinder. Si necesita ayuda con esto, cree una nueva pregunta (no tendré espacio para responda en los comentarios). Además, no estoy seguro de que deba usar @PersistentContext, solo use @Inject para todo
joscarsson
Sí, EntityManager es específico de JPA (Java EE). Gracias por tu comentario, abriré otra pregunta si me encuentro con un problema específico.
Johannes Staehlin
Solo para que conste, JPA también se ejecuta en Java SE. oracle.com/technetwork/java/javaee/tech/…
prefabSOFT
2
¿Qué hace Bind? ¿Qué pasa si tengo una interfaz y una implementación?
Dejell
52

Primero solo para responder un comentario en la respuesta de aceptación.

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

Simplemente lee bind( implementation ).to( contract ). Puede cadena alternativa .in( scope ). Alcance predeterminado de PerLookup. Entonces, si quieres un singleton, puedes

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

También hay un RequestScopeddisponible

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


Añadiendo a la respuesta aceptada

Para aquellos que intentan averiguar cómo registrar su AbstractBinderimplementación en su web.xml (es decir, no está usando a ResourceConfig), parece que el aglutinante 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 esto tampoco

<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 @Provideranotación debe permitir que el Featureescaneo del paquete lo recoja. O sin escanear el paquete, puede registrar explícitamente el Featureen elweb.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>

Ver también:

y para obtener información general de la documentación de Jersey


ACTUALIZAR

Suerte

Además del enlace básico en la respuesta aceptada, también tiene fábricas, donde puede tener una lógica de creación más compleja y también tener acceso para 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 MyServiceen su clase de recursos.

Paul Samsotha
fuente
Podría registrar mi clase de carpeta solo a través de una implementación de ResourceConfig, como se muestra en la respuesta aceptada. No se necesitaba ninguna clase de entidad.
Patrick Koorevaar
El uso web.xmlaunque se llame a configure()on Hk2Feature, la solicitud de recurso arroja un NullPointerException. @PaulSamsotha
bytesandcaffeine
12

La respuesta seleccionada data de hace un tiempo. No es práctico declarar todas las encuadernaciones en una encuadernación HK2 personalizada. Estoy usando Tomcat y solo tuve que agregar una dependencia. Aunque fue diseñado para Glassfish, encaja 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 ).

otonglet
fuente
La última línea (asegúrese de que su contenedor también esté configurado correctamente) es un poco vaga. ¿Alguna ayuda aquí? ¿Qué anotaciones usamos donde?
markthegrea
Estábamos usando Weld para la inyección de dependencias, lo que requería una configuración especial para trabajar con Tomcat (nuestro "contenedor" de aplicación). Si está utilizando Spring, funciona de inmediato.
otonglet
5

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 bean ApplicationScoped.

Espero que esto ayude a alguien.

gjijon
fuente
3

Oracle recomienda agregar la anotación @Path a todos los tipos que se inyectarán 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 al inicio), decidí tomar esta ruta, lo que me evita mantener todos los tipos admitidos 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() {..}
}
Benjamin Mesing
fuente
1
Link está muerto, debería señalar aquí
Hank
0

Para mí, funciona sin el AbstractBindersi incluyo las siguientes dependencias en mi aplicación web (que se ejecuta en 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>

Funciona con CDI 1.2 / CDI 2.0 para mí (usando Weld 2/3 respectivamente).

jansohn
fuente
0

Se requiere dependencia para un servicio reparador de Jersey y Tomcat es el servidor. donde $ {jersey.version} es 2.29.1

    <dependency>
        <groupId>javax.enterprise</groupId>
        <artifactId>cdi-api</artifactId>
        <version>2.0.SP1</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>org.glassfish.jersey.core</groupId>
        <artifactId>jersey-server</artifactId>
        <version>${jersey.version}</version>
    </dependency>
    <dependency>
        <groupId>org.glassfish.jersey.containers</groupId>
        <artifactId>jersey-container-servlet</artifactId>
        <version>${jersey.version}</version>
    </dependency>
    <dependency>
        <groupId>org.glassfish.jersey.inject</groupId>
        <artifactId>jersey-hk2</artifactId>
        <version>${jersey.version}</version>
    </dependency>

El código básico será el siguiente:

@RequestScoped
@Path("test")
public class RESTEndpoint {

   @GET
   public String getMessage() {
alokj
fuente