Inyección de dependencia con Jersey 2.0

Resuelto donnie_armstrong asked hace 11 años • 8 respuestas

Comenzando desde cero sin ningún conocimiento previo de Jersey 1.x, me cuesta 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 proporcione mi recurso, pero tan pronto como agrego @Inject a MyService, el marco genera 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 avatar Apr 25 '13 20:04 donnie_armstrong
Aceptado

Debe definir AbstractBindery registrarlo en su aplicación JAX-RS. La carpeta 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.classse crea una instancia utilizando la clase MyService. Para utilizar esta carpeta, debe estar registrada en 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");
    }
}

La carpeta que especifica la inyección de dependencia 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) mediante la packages()llamada al método.

joscarsson avatar Jun 16 '2013 11:06 joscarsson

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 se lee bind( implementation ).to( contract ). Puedes cadena alternativa .in( scope ). Alcance predeterminado de PerLookup. Entonces, si quieres un singleton, puedes

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

También hay RequestScopeddisponible

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


Agregando a la respuesta aceptada

Para aquellos que intentan descubrir cómo registrar su AbstractBinderimplementación en su web.xml (es decir, no están usando un ResourceConfig), parece que la carpeta no se descubrirá mediante el 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 detecte. O sin escanear paquetes, 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:

  • Inyección de parámetros de método personalizado con Jersey
  • ¿Cómo inyectar un objeto en el contexto de solicitud de camiseta?
  • ¿Cómo configuro correctamente un EntityManager en una aplicación jersey/hk2?
  • Solicitar inyección con alcance en singletons

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

  • Inyección personalizada y gestión del ciclo de vida

ACTUALIZAR

Fábricas

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 puedes inyectar MyServiceen tu clase de recurso.

Paul Samsotha avatar Mar 26 '2015 10:03 Paul Samsotha