Inserimento di dipendenze con Jersey 2.0


108

Partendo da zero senza alcuna conoscenza precedente di Jersey 1.x, ho difficoltà a capire come impostare l'inserimento delle dipendenze nel mio progetto Jersey 2.0.

Capisco anche che HK2 è disponibile in Jersey 2.0, ma non riesco a trovare documenti che aiutino con l'integrazione di 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>

Posso avviare il container e servire la mia risorsa, ma non appena aggiungo @Inject a MyService, il framework genera un'eccezione:

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)


Il mio progetto iniziale è disponibile su GitHub: https://github.com/donaldjarmstrong/jaxrs

Risposte:


107

È necessario definire AbstractBindere registrarlo nell'applicazione JAX-RS. Il binder specifica come l'inserimento delle dipendenze dovrebbe creare le tue classi.

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

Quando @Injectviene rilevato su un parametro o un campo di tipo MyService.class, viene istanziato utilizzando la classe MyService. Per utilizzare questo raccoglitore, è necessario registrarlo con l'applicazione JAX-RS. Nel tuo web.xml, definisci un'applicazione JAX-RS come questa:

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

Implementa la MyApplicationclasse (specificata sopra in init-param).

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

Il binder che specifica l'inserimento delle dipendenze è registrato nel costruttore della classe e viene inoltre indicato all'applicazione dove trovare le risorse REST (nel tuo caso MyResource) utilizzando la packages()chiamata al metodo.


1
E l'EntityManager? Qualche suggerimento su come associarlo, quindi posso iniettarlo tramite @PersistenceContext?
Johannes Staehlin

4
Non sono sicuro di cosa EntityManagersia, ma a giudicare da docs.oracle.com/javaee/6/api/javax/persistence/… sembra essere un'interfaccia. È possibile associare utilizzando bind(EntityManagerImpl.class).to(EntityManager.class)(che legherà una classe EntityManagerImplche implementa l'interfaccia EntityManager. Se è necessario utilizzare una fabbrica, dare un'occhiata a bindFactory()nel AbstractBinder. Se hai bisogno di aiuto con questo, si prega di creare una nuova domanda (non avrò spazio per rispondi nei commenti). Inoltre, non sono sicuro che dovresti usare @PersistentContext, usa solo @Inject per tutto
joscarsson

Sì, EntityManager è specifico per JPA (Java EE). Grazie per il tuo commento, aprirò un'altra domanda se mi imbatto in un problema specifico!
Johannes Staehlin

Solo per la cronaca, JPA funziona anche su Java SE. oracle.com/technetwork/java/javaee/tech/…
prefabSOFT

2
Cosa fa bind? Cosa succede se ho un'interfaccia e un'implementazione?
Dejell

52

Prima solo per rispondere a un commento nella risposta accetta.

"Cosa fa il bind? E se ho un'interfaccia e un'implementazione?"

Si legge semplicemente bind( implementation ).to( contract ). Puoi catena alternativa .in( scope ). Ambito predefinito di PerLookup. Quindi, se vuoi un singleton, puoi

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

C'è anche un RequestScopeddisponibile

Inoltre, invece di bind(Class).to(Class), puoi anche bind(Instance).to(Class), che sarà automaticamente un singleton.


Aggiunta alla risposta accettata

Per coloro che cercano di capire come registrare la tua AbstractBinderimplementazione nel tuo web.xml (cioè non stai usando a ResourceConfig), sembra che il raccoglitore non verrà scoperto attraverso la scansione del pacchetto, cioè

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

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

Per farlo funzionare, ho dovuto implementare 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' @Providerannotazione dovrebbe consentire Featuredi essere prelevata dalla scansione del pacco. Oppure senza la scansione del pacchetto, è possibile registrare esplicitamente Featureil fileweb.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>

Guarda anche:

e per informazioni generali dalla documentazione Jersey


AGGIORNARE

fabbriche

A parte l'associazione di base nella risposta accettata, hai anche fabbriche, dove puoi avere una logica di creazione più complessa e avere anche accesso per richiedere informazioni sul contesto. Per esempio

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

Quindi puoi iniettare MyServicenella tua classe di risorse.


Potrei registrare la mia classe binder solo tramite un'implementazione ResourceConfig, come mostrato nella risposta accettata. Non era necessaria alcuna classe di funzionalità.
Patrick Koorevaar

Utilizzando web.xmlanche se l' configure()on Hk2Featuresi chiama, richiedendo risorse getta una NullPointerException. @PaulSamsotha
bytesandcaffeine

12

La risposta selezionata risale a qualche tempo fa. Non è pratico dichiarare ogni rilegatura in un raccoglitore HK2 personalizzato. Sto usando Tomcat e ho dovuto aggiungere solo una dipendenza. Anche se è stato progettato per Glassfish, si adatta perfettamente ad altri contenitori.

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

Assicurati che anche il tuo contenitore sia configurato correttamente ( consulta la documentazione ).


L'ultima riga (assicurati che anche il tuo contenitore sia configurato correttamente) è un po 'vaga. Qualche aiuto qui? Quali annotazioni usiamo dove?
markthegrea

Stavamo usando Weld per l'inserimento delle dipendenze che richiedeva una configurazione speciale per funzionare con Tomcat (il nostro "contenitore" dell'applicazione). Se stai usando Spring, funziona fuori dagli schemi.
otonglet

5

In ritardo ma spero che questo aiuti qualcuno.

Ho il mio JAX RS definito in questo modo:

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

Quindi, nel mio codice finalmente posso iniettare:

@Inject
SomeManagedBean bean;

Nel mio caso, il file SomeManagedBean è un bean ApplicationScoped.

Spero che questo aiuti a chiunque.


3

Oracle consiglia di aggiungere l'annotazione @Path a tutti i tipi da iniettare quando si combina JAX-RS con CDI: http://docs.oracle.com/javaee/7/tutorial/jaxrs-advanced004.htm Sebbene questo sia tutt'altro che perfetto ( ad esempio, riceverai un avviso da Jersey all'avvio), ho deciso di prendere questa strada, che mi evita di mantenere tutti i tipi supportati all'interno di un raccoglitore.

Esempio:

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

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

  @GET
  public Object get() {..}
}

1
Link è morto, dovrebbe indicare qui
Hank


0

Per me funziona senza AbstractBinderse includo le seguenti dipendenze nella mia applicazione web (in esecuzione su 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>

Per me funziona con CDI 1.2 / CDI 2.0 (utilizzando rispettivamente Weld 2/3).


0

Dipendenza richiesta per il servizio jersey restful e Tomcat è il server. dove $ {jersey.version} è 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>

Il codice di base sarà il seguente:

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

   @GET
   public String getMessage() {
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.