Wstrzyknięcie zależności z Jersey 2.0


108

Zaczynając od zera bez wcześniejszej wiedzy na temat Jersey 1.x, trudno mi zrozumieć, jak skonfigurować wstrzykiwanie zależności w moim projekcie Jersey 2.0.

Rozumiem również, że HK2 jest dostępny w Jersey 2.0, ale nie mogę znaleźć dokumentów, które pomogłyby w integracji 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>

Mogę uruchomić kontener i udostępnić mój zasób, ale gdy tylko dodam @Inject do MyService, framework zgłasza wyjątek:

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)


Mój projekt startowy jest dostępny na GitHub: https://github.com/donaldjarmstrong/jaxrs

Odpowiedzi:


107

Musisz zdefiniować AbstractBinderi zarejestrować go w aplikacji JAX-RS. Segregator określa, w jaki sposób iniekcja zależności powinna tworzyć klasy.

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

Gdy @Injectzostanie wykryty w parametrze lub polu typu MyService.class, jest tworzony przy użyciu klasy MyService. Aby korzystać z tego segregatora, należy go zarejestrować w aplikacji JAX-RS. W swoim web.xmlzdefiniuj aplikację JAX-RS w następujący sposób:

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

Zaimplementuj MyApplicationklasę (określoną powyżej w init-param).

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

Spoiwo określające iniekcję zależności jest rejestrowane w konstruktorze klasy, a także informujemy aplikację, gdzie ma znaleźć zasoby REST (w twoim przypadku MyResource) za pomocą packages()wywołania metody.


1
A co z EntityManager? Jakaś wskazówka, jak to powiązać, więc mogę wstrzyknąć przez @PersistenceContext?
— Johannes Staehlin

4
Nie jestem pewien, co to EntityManagerjest, ale sądząc po docs.oracle.com/javaee/6/api/javax/persistence/ ... wydaje się, że jest to interfejs. Możesz go powiązać za pomocą bind(EntityManagerImpl.class).to(EntityManager.class)(co spowoduje związanie klasy EntityManagerImplimplementującej interfejs EntityManager. Jeśli potrzebujesz użyć fabryki, zajrzyj bindFactory()do AbstractBinder. Jeśli potrzebujesz pomocy, utwórz nowe pytanie (nie będę mieć miejsca na odpowiedz na to w komentarzach) .Nie jestem również pewien, czy powinieneś używać @PersistentContext, po prostu używaj @Inject do wszystkiego
— joscarsson

Tak, EntityManager jest specyficzny dla JPA (Java EE). Dziękuję za komentarz, otworzę kolejne pytanie, jeśli napotkam konkretny problem!
— Johannes Staehlin

Dla przypomnienia, JPA działa również w Javie SE. oracle.com/technetwork/java/javaee/tech/ ...
— prefabSOFT

2
Co robi bind? A jeśli mam interfejs i implementację?
— Dejell

52

Najpierw wystarczy odpowiedzieć na komentarz w odpowiedzi akceptuje.

„Co robi bind? A jeśli mam interfejs i implementację?”

Po prostu czyta bind( implementation ).to( contract ). Możesz alternatywnie łańcuch .in( scope ). Domyślny zakres PerLookup. Więc jeśli chcesz singletona, możesz

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

Jest też RequestScopeddostępny

Zamiast tego bind(Class).to(Class)możesz też bind(Instance).to(Class), co automatycznie będzie singletonem.


Dodawanie do zaakceptowanej odpowiedzi

Dla tych, którzy próbują dowiedzieć się, jak zarejestrować twoją AbstractBinderimplementację w twoim web.xml (tj. Nie używasz a ResourceConfig), wydaje się, że spinacz nie zostanie wykryty przez skanowanie pakietów, tj.

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

Albo to

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

Aby to zadziałało, musiałem zaimplementować 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;
    }
}

@ProviderAdnotacja powinna umożliwić Featurebyć odebrany przez skanowanie pakietu. Lub bez skanowania pakietów, możesz jawnie zarejestrować plik Featurewweb.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>

Zobacz też:

oraz ogólne informacje z dokumentacji Jersey


AKTUALIZACJA

Fabryki

Oprócz podstawowego powiązania w zaakceptowanej odpowiedzi masz również fabryki, w których możesz mieć bardziej złożoną logikę tworzenia, a także mieć dostęp do żądań informacji kontekstowych. Na przykład

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

Następnie możesz wstrzyknąć MyServicedo swojej klasy zasobów.


Mogę zarejestrować moją klasę segregatora tylko za pośrednictwem implementacji ResourceConfig, jak pokazano w zaakceptowanej odpowiedzi. Żadna klasa funkcji nie była potrzebna.
— Patrick Koorevaar

Korzystanie web.xmlchoć configure()on Hk2Featurenazywa, żądając zasobów rzuca NullPointerException. @PaulSamsotha
— bytesandcaffeine

12

Wybrana odpowiedź pochodzi sprzed jakiegoś czasu. Zadeklarowanie każdego powiązania w niestandardowym segregatorze HK2 nie jest praktyczne. Używam Tomcat i po prostu musiałem dodać jedną zależność. Mimo że został zaprojektowany dla Glassfisha, doskonale pasuje do innych pojemników.

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

Upewnij się, że kontener jest również prawidłowo skonfigurowany ( zobacz dokumentację ).


Ostatni wiersz (Upewnij się, że kontener też jest odpowiednio skonfigurowany) jest nieco niejasny. Jakaś pomoc tutaj? Jakich adnotacji używamy gdzie?
— markthegrea

Używaliśmy Weld do wstrzykiwania zależności, co wymagało specjalnej konfiguracji do pracy z Tomcat (nasz „kontener” aplikacji). Jeśli używasz Spring, działa po wyjęciu z pudełka.
— otonglet

5

Późno, ale mam nadzieję, że to komuś pomoże.

Mam moje JAX RS zdefiniowane w ten sposób:

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

W końcu w swoim kodzie mogę wstrzyknąć:

@Inject
SomeManagedBean bean;

W moim przypadku SomeManagedBeanjest to komponent bean ApplicationScoped.

Mam nadzieję, że to pomoże każdemu.


3

Oracle zaleca dodanie adnotacji @Path do wszystkich typów, które mają być wstrzykiwane podczas łączenia JAX-RS z CDI: http://docs.oracle.com/javaee/7/tutorial/jaxrs-advanced004.htm Chociaż jest to dalekie od ideału ( np. otrzymasz ostrzeżenie z Jersey przy starcie), zdecydowałem się na tę trasę, co oszczędza mi utrzymywania wszystkich obsługiwanych typów w segregatorze.

Przykład:

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

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

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

1
Link nie żyje, powinien wskazywać tutaj
— Hank


0

U mnie działa bez, AbstractBinderjeśli dołączę następujące zależności w mojej aplikacji internetowej (działającej na 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>

Działa z CDI 1.2 / CDI 2.0 dla mnie (używając odpowiednio Weld 2/3).


0

Zależność wymagana do pełnej obsługi koszulki, a Tomcat jest serwerem. gdzie $ {jersey.version} to 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>

Podstawowy kod będzie wyglądał następująco:

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

   @GET
   public String getMessage() {
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.