Injection de dépendance avec Jersey 2.0

108

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

Je comprends également que HK2 est disponible dans Jersey 2.0, mais je n'arrive pas à trouver des documents qui aident à 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 démarrer le conteneur et servir ma ressource, mais dès que j'ajoute @Inject à 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

donnie_armstrong
la source

Réponses:

107

Vous devez définir un AbstractBinderet l'enregistrer dans votre application JAX-RS. Le classeur spécifie comment l'injection de dépendances doit créer vos classes.

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

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

<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 MyApplicationclasse (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épendances est enregistré dans le constructeur de la classe, et nous indiquons également à l'application où trouver les ressources REST (dans votre cas MyResource) à l'aide de l' packages()appel de méthode.

Joscarsson
la source
1
Qu'en est-il de l'EntityManager? Un indice sur la façon de le lier, afin que je puisse l'injecter via @PersistenceContext?
Johannes Staehlin
4
Je ne suis pas sûr de ce que EntityManagerc'est, mais à en juger par docs.oracle.com/javaee/6/api/javax/persistence/ ... cela semble être une interface. Vous pouvez le lier en utilisant bind(EntityManagerImpl.class).to(EntityManager.class)(qui liera une classe EntityManagerImplimplémentant l'interface EntityManager. Si vous avez besoin d'utiliser une fabrique, jetez un œil bindFactory()dans le AbstractBinder. Si vous avez besoin d'aide, créez une nouvelle question (je n'aurai pas la place pour Répondez-y dans les commentaires). De plus, je ne suis pas sûr que vous devriez utiliser @PersistentContext, utilisez simplement @Inject pour tout
joscarsson
Ouais, EntityManager est spécifique à JPA (Java EE). Merci pour votre commentaire, je vais ouvrir une autre question si je rencontre un problème spécifique!
Johannes Staehlin
Pour mémoire, JPA fonctionne également sur Java SE. oracle.com/technetwork/java/javaee/tech/…
prefabSOFT
2
Que fait bind? Et si j'ai une interface et une implémentation?
Dejell
53

Tout d'abord pour répondre à un commentaire dans la réponse accepte.

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

Il lit simplement bind( implementation ).to( contract ). Vous pouvez chaîne alternative .in( scope ). Portée par défaut de PerLookup. Donc si tu veux un singleton, tu peux

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

Il y a aussi un RequestScopeddisponible

En outre, 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 AbstractBinderimplémentation dans votre web.xml (c'est-à-dire que vous n'utilisez pas a ResourceConfig), il semble que le classeur ne sera pas découvert par l'analyse des paquets, c'est-à-dire

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

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

Pour le faire fonctionner, 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' @Providerannotation doit permettre Featureà la numérisation de l'emballage. Ou sans analyse de package, vous pouvez enregistrer explicitement le Featuredans leweb.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 les informations générales 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 avoir également accès aux informations de contexte de 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 MyServicedans votre classe de ressources.

Paul Samsotha
la source
Je pourrais enregistrer ma classe de classeur uniquement via une implémentation ResourceConfig, comme indiqué dans la réponse acceptée. Aucune classe d'entités n'était nécessaire.
Patrick Koorevaar
En utilisant web.xmlmême si configure()on Hk2Featureest appelé, la demande de ressource lève un NullPointerException. @PaulSamsotha
bytesandcaffeine
12

La réponse choisie remonte à un certain temps. Il n'est pas pratique de déclarer chaque liaison dans un classeur HK2 personnalisé. J'utilise Tomcat et je devais juste 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 que votre conteneur est également correctement configuré ( voir la documentation ).

otonglet
la source
La dernière ligne (Assurez-vous que votre conteneur est également correctement configuré) est un peu vague. Une aide ici? Quelles annotations utilisons-nous où?
markthegrea
Nous utilisions Weld pour l'injection de dépendances qui nécessitait une configuration spéciale pour fonctionner avec Tomcat (notre application «conteneur»). Si vous utilisez Spring, cela fonctionne immédiatement.
otonglet le
5

Tard 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, le SomeManagedBeanest un bean ApplicationScoped.

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

Gjijon
la source
3

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, vous recevrez un avertissement de Jersey au démarrage), j'ai décidé de prendre cette route, 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() {..}
}
Benjamin Mesing
la source
1
Le lien est mort, devrait pointer ici
Hank
0

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

guice-bridge-jit-injector

Choi
la source
0

Pour moi, cela fonctionne sans le AbstractBindersi j'inclus les dépendances suivantes dans mon application Web (fonctionnant 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 respectivement Weld 2/3).

Jansohn
la source
0

Dépendance requise pour le service reposant du maillot et Tomcat est le serveur. où $ {jersey.version} est 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>

Le code de base sera le suivant:

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

   @GET
   public String getMessage() {
alokj
la source