Abhängigkeitsinjektion mit Jersey 2.0


108

Wenn ich ohne Vorkenntnisse in Jersey 1.x von vorne anfange, fällt es mir schwer zu verstehen, wie die Abhängigkeitsinjektion in meinem Jersey 2.0-Projekt eingerichtet wird.

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

@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 starten und meine Ressource bereitstellen, 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 bei GitHub verfügbar: https://github.com/donaldjarmstrong/jaxrs

Antworten:


107

Sie müssen ein definieren AbstractBinderund in Ihrer JAX-RS-Anwendung registrieren. Der Ordner 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 @Injectein Parameter oder ein Feld vom Typ erkannt wird MyService.class, wird er mithilfe der Klasse instanziiert MyService. Um diesen Ordner verwenden zu können, muss er bei der JAX-RS-Anwendung registriert sein. web.xmlDefinieren Sie in Ihrem 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 MyApplicationKlasse (oben in init-param).

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

Der Ordner, der die Abhängigkeitsinjektion angibt, wird im Konstruktor der Klasse registriert, und wir teilen der Anwendung MyResourcemit dem packages()Methodenaufruf mit, wo sich die REST-Ressourcen (in Ihrem Fall ) befinden .


1
Was ist mit dem EntityManager? Gibt es einen Hinweis, wie man es bindet, damit ich es über @PersistenceContext injizieren kann?
Johannes Staehlin

4
Ich bin mir nicht sicher, was es EntityManagerist, aber nach docs.oracle.com/javaee/6/api/javax/persistence/… zu urteilen , scheint es eine Schnittstelle zu sein. Sie können es mit binden bind(EntityManagerImpl.class).to(EntityManager.class)(wodurch eine Klasse EntityManagerImplgebunden wird, die die Schnittstelle implementiert EntityManager. Wenn Sie eine Factory verwenden müssen, schauen Sie bindFactory()in die AbstractBinder. Wenn Sie dabei Hilfe benötigen, erstellen Sie bitte eine neue Frage (ich habe keinen Platz dafür Beantworten Sie es in den Kommentaren. Außerdem bin ich mir nicht sicher, ob Sie @PersistentContext verwenden sollten. Verwenden Sie einfach @Inject für alles
joscarsson

Ja, EntityManager ist JPA (Java EE) spezifisch. Vielen Dank für Ihren Kommentar, ich werde eine weitere Frage öffnen, wenn ich auf ein bestimmtes Problem stoße!
Johannes Staehlin

Nur zur Veranschaulichung: JPA läuft auch auf Java SE. oracle.com/technetwork/java/javaee/tech/…
prefabSOFT

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

52

Zuerst nur um einen Kommentar in der akzeptierten Antwort zu beantworten.

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

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

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

Es gibt auch eine zur RequestScopedVerfügung

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


Hinzufügen zur akzeptierten Antwort

Für diejenigen, die herausfinden AbstractBindermöchten , wie Sie Ihre Implementierung in Ihrer web.xml registrieren können (dh Sie verwenden keine ResourceConfig), wird der Ordner anscheinend 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 nicht

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

Die @ProviderAnmerkung sollte es ermöglichen Feature, dass das Paket beim Scannen des Pakets erfasst wird. Oder ohne Paket Scannen können Sie explizit das Register Featurein derweb.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>

Siehe auch:

und für allgemeine Informationen aus der Jersey-Dokumentation


AKTUALISIEREN

Fabriken

Abgesehen von der grundlegenden Bindung in der akzeptierten Antwort verfügen Sie auch über Fabriken, in denen Sie über eine komplexere Erstellungslogik verfügen und auf Kontextinformationen anfordern können. Beispielsweise

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 MyServicein Ihre Ressourcenklasse einfügen.


Ich konnte meine Binder-Klasse nur über eine ResourceConfig-Implementierung registrieren, wie in der akzeptierten Antwort gezeigt. Es wurde keine Feature-Class benötigt.
Patrick Koorevaar

Mit web.xmlobwohl die configure()auf Hk2Featuregenannt wird, Ressource anfordernden wirft ein NullPointerException. @ PaulSamsotha
Bytesandcaffeine

12

Die ausgewählte Antwort stammt aus einer Weile zurück. Es ist nicht praktisch, jede Bindung in einem benutzerdefinierten HK2-Binder zu deklarieren. Ich verwende Tomcat und musste nur eine Abhängigkeit hinzufügen. Obwohl es für Glassfish entwickelt 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 ordnungsgemäß konfiguriert ist ( siehe Dokumentation ).


Die letzte Zeile (Stellen Sie sicher, dass auch Ihr Container richtig konfiguriert ist) ist etwas vage. Hilfe hier? Welche Anmerkungen verwenden wir wo?
Märzhegrea

Wir haben Weld für die Abhängigkeitsinjektion verwendet, für die eine spezielle Konfiguration erforderlich war, um mit Tomcat (unserem Anwendungscontainer) zu arbeiten. Wenn Sie Spring verwenden, funktioniert es sofort.
otonglet

5

Spät, aber ich hoffe das hilft jemandem.

Ich habe mein JAX RS folgendermaßen definiert:

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

Dann kann ich in meinen Code endlich einspritzen:

@Inject
SomeManagedBean bean;

In meinem Fall SomeManagedBeanhandelt es sich um eine ApplicationScoped-Bean.

Hoffe das hilft jedem.


3

Oracle empfiehlt, die @ Path-Annotation allen zu injizierenden Typen hinzuzufügen, wenn JAX-RS mit CDI kombiniert wird: http://docs.oracle.com/javaee/7/tutorial/jaxrs-advanced004.htm Dies ist jedoch alles andere als perfekt ( Wenn Sie beispielsweise beim Start eine Warnung von Jersey erhalten, habe ich mich für diese Route entschieden, wodurch ich nicht alle unterstützten Typen in einem Ordner verwalten kann.

Beispiel:

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

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

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

1
Link ist tot, sollte hier zeigen
Hank


0

Bei mir funktioniert es ohne die AbstractBinderfolgenden Abhängigkeiten in meiner Webanwendung (läuft unter 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>

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


0

Für den erholsamen Trikot-Service ist eine Abhängigkeit erforderlich, und Tomcat ist der Server. Dabei ist $ {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>

Der Basiscode lautet wie folgt:

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

   @GET
   public String getMessage() {
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.