webentwicklung-frage-antwort-db.com.de

Abhängigkeitsinjektion mit Jersey 2.0

Ich beginne ohne jegliche vorherige Jersey 1.x-Kenntnisse von Grund auf und verstehe, wie ich die Abhängigkeitsinjektion in meinem Jersey 2.0-Projekt einrichten kann. 

Ich verstehe auch, dass HK2 in Jersey 2.0 verfügbar ist, aber ich finde anscheinend keine Dokumente, die bei der Integration von Jersey 2.0 hilfreich sind.

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

Ich kann den Container dazu bringen, meine Ressource zu starten und bereitzustellen, aber sobald ich @Inject zu MyService hinzufüge, löst das Framework eine Ausnahme aus:

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)


Mein Starterprojekt ist unter GitHub verfügbar: https://github.com/donaldjarmstrong/jaxrs

96

Sie müssen eine AbstractBinder definieren und in Ihrer JAX-RS-Anwendung registrieren. Die Sammelmappe gibt an, wie die Abhängigkeitsinjektion Ihre Klassen erstellen soll.

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

Wenn @Inject für einen Parameter oder ein Feld des Typs MyService.class erkannt wird, wird er mit der Klasse MyService instanziiert. Um diese Mappe verwenden zu können, muss sie bei der JAX-RS-Anwendung registriert sein. Definieren Sie in Ihrem web.xml eine JAX-RS-Anwendung wie folgt:

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

Implementieren Sie die MyApplication-Klasse (oben im init-param angegeben).

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

Die bindungsspezifizierende Abhängigkeitseinspritzung wird im Konstruktor der Klasse registriert. Außerdem wird der Anwendung mit Hilfe des Methodenaufrufs packages() mitgeteilt, wo die Ressourcen REST (in Ihrem Fall MyResource) zu finden sind.

97
joscarsson

Um nur einen Kommentar in der Antwort anzunehmen.

"Was macht bind? Was ist, wenn ich eine Schnittstelle und eine Implementierung habe?"

Es liest einfach bind( implementation ).to( contract ). Sie können die alternative Kette .in( scope ). Standardbereich von PerLookup. Wenn Sie also ein Singleton wollen, können Sie

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

Es ist auch eine RequestScoped verfügbar

Anstelle von bind(Class).to(Class) können Sie auch bind(Instance).to(Class) verwenden, die automatisch ein Singleton sein wird.


Hinzufügen zur akzeptierten Antwort

Für diejenigen, die herausfinden möchten, wie Sie Ihre AbstractBinder-Implementierung in Ihrer web.xml registrieren (d. H. Sie verwenden keine ResourceConfig), scheint es, als würden die Ordner nicht durch das Scannen von Paketen entdeckt, d. H. 

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

Oder das auch

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

Damit es funktioniert, musste ich eine Feature implementieren:

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

Die @Provider-Annotation sollte es ermöglichen, dass die Feature vom Paketscan abgeholt wird. Ohne das Scannen von Paketen können Sie die Feature explizit im web.xml registrieren.

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

Siehe auch:

und für allgemeine Informationen aus der Jersey-Dokumentation


AKTUALISIEREN

Fabriken

Abgesehen von der grundlegenden Bindung in der akzeptierten Antwort gibt es auch Fabriken, in denen Sie eine komplexere Erstellungslogik haben können und auch Zugriff auf Kontextinformationen anfordern können. Zum Beispiel

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

Dann können Sie MyService in Ihre Ressourcenklasse einfügen.

45
Paul Samsotha

Die ausgewählte Antwort stammt aus einer Weile zurück. Es ist nicht praktisch, jede Bindung in einer benutzerdefinierten HK2-Mappe zu deklarieren. Ich benutze Tomcat und musste nur eine Abhängigkeit hinzufügen. Obwohl es für Glassfish entworfen wurde, passt es perfekt in andere Behälter. 

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

Stellen Sie sicher, dass auch Ihr Container richtig konfiguriert ist ( siehe Dokumentation ).

11
otonglet

Spät aber ich hoffe das hilft jemandem.

Ich habe meinen JAX RS folgendermaßen definiert:

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

Dann kann ich endlich in meinem Code spritzen:

@Inject
SomeManagedBean bean;

In meinem Fall ist SomeManagedBean eine ApplicationScoped-Bean.

Hoffe, das hilft jedem.

5
gjijon

Oracle empfiehlt, die @Path-Annotation zu allen Typen hinzuzufügen, die injiziert werden sollen, wenn JAX-RS mit CDI kombiniert wird: http://docs.Oracle.com/javaee/7/tutorial/jaxrs-advanced004.htm Obwohl dies alles andere als perfekt ist (z. B. werden Sie beim Start von Jersey gewarnt), entschied ich mich für diesen Weg, der mich davon abhält, alle unterstützten Typen in einer Sammelmappe zu behalten.

Beispiel:

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

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

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

Für mich funktioniert es ohne die AbstractBinder, wenn ich in meiner Webanwendung (unter Tomcat 8.5, Jersey 2.27) die folgenden Abhängigkeiten aufnehme:

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

Es funktioniert für mich mit CDI 1.2/CDI 2.0 (jeweils mit Weld 2/3).

0
lazlev

Wenn Sie lieber Guice verwenden und nicht alle Bindungen deklarieren möchten, können Sie auch diesen Adapter versuchen:

Guice-Bridge-Jit-Injektor

0
Choi