web-dev-qa-db-fra.com

Injection de dépendance avec Jersey 2.0

Partant de zéro sans aucune connaissance préalable de Jersey 1.x, j'ai du mal à comprendre comment configurer l’injection de dépendance dans mon projet Jersey 2.0. 

Je comprends également que HK2 est disponible dans Jersey 2.0, mais je ne trouve pas les docs utiles pour l’intégration 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>

Je peux faire en sorte que le conteneur démarre et serve ma ressource, mais dès que j'ajoute @Inject to MyService, le framework lève une exception:

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)


Mon projet de démarrage est disponible sur GitHub: https://github.com/donaldjarmstrong/jaxrs

96
donnie_armstrong

Vous devez définir une AbstractBinder et l'enregistrer dans votre application JAX-RS. Le classeur spécifie comment l'injection de dépendance doit créer vos classes.

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

Lorsque @Inject est détecté sur un paramètre ou un champ de type MyService.class, il est instancié à l'aide de la classe MyService. Pour utiliser ce classeur, il doit être enregistré avec l'application JAX-RS. Dans votre web.xml, définissez une application JAX-RS comme suit:

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

Implémentez la classe MyApplication (spécifiée ci-dessus dans le init-param).

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

Le classeur spécifiant l'injection de dépendance est enregistré dans le constructeur de la classe. Nous indiquons également à l'application où trouver les ressources REST (dans votre cas, MyResource) à l'aide de l'appel de méthode packages().

97
joscarsson

D'abord, juste pour répondre à un commentaire dans la réponse acceptée.

"Que fait bind? Et si j'ai une interface et une implémentation?"

Il se lit simplement bind( implementation ).to( contract ). Vous pouvez utiliser la chaîne alternative .in( scope ). Portée par défaut de PerLookup. Donc, si vous voulez un singleton, vous pouvez

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

Il y a aussi une RequestScoped disponible

De plus, au lieu de bind(Class).to(Class), vous pouvez également bind(Instance).to(Class), qui sera automatiquement un singleton.


Ajout à la réponse acceptée

Pour ceux qui essaient de comprendre comment enregistrer votre implémentation AbstractBinder dans votre web.xml (c’est-à-dire que vous n’utilisez pas ResourceConfig), il semble que le classeur ne sera pas découvert lors de l’analyse des packages, c.-à-d. 

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

Ou ce soit

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

Pour que cela fonctionne, j'ai dû implémenter 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;
    }
}

L'annotation @Provider devrait permettre à Feature d'être récupérée par l'analyse du package. Ou sans analyse de paquet, vous pouvez enregistrer explicitement la Feature dans le 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>

Voir également:

et pour des informations générales tirées de la documentation Jersey


METTRE À JOUR

Des usines

Outre la liaison de base dans la réponse acceptée, vous avez également des usines dans lesquelles vous pouvez avoir une logique de création plus complexe et également avoir accès aux informations de contexte de la demande. Par exemple

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

Ensuite, vous pouvez injecter MyService dans votre classe de ressources.

45
Paul Samsotha

La réponse sélectionnée date d'un certain temps. Il n'est pas pratique de déclarer chaque liaison dans un classeur HK2 personnalisé . J'utilise Tomcat et je n'ai eu qu'à ajouter une dépendance. Même s'il a été conçu pour Glassfish, il s'intègre parfaitement dans d'autres conteneurs. 

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

Assurez-vous également que votre conteneur est correctement configuré ( voir la documentation ).

11
otonglet

En retard mais j'espère que cela aide quelqu'un.

J'ai mon JAX RS défini comme ceci:

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

Ensuite, dans mon code, je peux enfin injecter:

@Inject
SomeManagedBean bean;

Dans mon cas, la SomeManagedBean est un bean ApplicationScoped.

J'espère que cela aide à n'importe qui.

5
gjijon

Oracle recommande d'ajouter l'annotation @Path à tous les types à injecter lors de la combinaison de JAX-RS avec CDI: http://docs.Oracle.com/javaee/7/tutorial/jaxrs-advanced004.htm Bien que cela soit loin d’être parfait (par exemple, Jersey vous avertira au démarrage), j’ai décidé de suivre cette voie, ce qui m’évite de conserver tous les types pris en charge dans un classeur.

Exemple:

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

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

  @GET
  public Object get() {..}
}
2
Benjamin Mesing

Pour moi, cela fonctionne sans la AbstractBinder si j'inclus les dépendances suivantes dans mon application Web (s'exécutant sur 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>

Cela fonctionne avec CDI 1.2/CDI 2.0 pour moi (en utilisant Weld 2/3 respectivement).

0
lazlev

Si vous préférez utiliser Guice et que vous ne voulez pas déclarer toutes les liaisons, vous pouvez également essayer cet adaptateur:

injecteur guice-bridge-jit

0
Choi