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

donnie_armstrong
źródło

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.

joscarsson
źródło
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.

Paul Samsotha
źródło
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ę ).

otonglet
źródło
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.

gjijon
źródło
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() {..}
}
Benjamin Mesing
źródło
1
Link nie żyje, powinien wskazywać tutaj
Hank
0

Jeśli wolisz używać Guice i nie chcesz deklarować wszystkich powiązań, możesz również wypróbować ten adapter:

guice-bridge-jit-injector

Choi
źródło
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).

jansohn
źródło
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() {
alokj
źródło