Injeksi ketergantungan dengan Jersey 2.0


108

Memulai dari awal tanpa pengetahuan Jersey 1.x sebelumnya, saya mengalami kesulitan memahami cara mengatur injeksi ketergantungan di proyek Jersey 2.0 saya.

Saya juga memahami bahwa HK2 tersedia di Jersey 2.0, tetapi sepertinya saya tidak dapat menemukan dokumen yang membantu integrasi 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>

Saya bisa mendapatkan wadah untuk memulai dan menyajikan sumber daya saya, tetapi segera setelah saya menambahkan @Inject ke MyService, kerangka kerja mengeluarkan pengecualian:

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)


Proyek awal saya tersedia di GitHub: https://github.com/donaldjarmstrong/jaxrs

Jawaban:


107

Anda perlu mendefinisikan AbstractBinderdan mendaftarkannya di aplikasi JAX-RS Anda. Binder menentukan bagaimana injeksi ketergantungan harus membuat kelas Anda.

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

Ketika @Injectterdeteksi pada parameter atau bidang tipe MyService.classitu dibuat menggunakan kelas MyService. Untuk menggunakan pengikat ini, pengikat harus didaftarkan dengan aplikasi JAX-RS. Di Anda web.xml, tentukan aplikasi JAX-RS seperti ini:

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

Implementasikan MyApplicationkelas (ditentukan di atas dalam init-param).

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

Binder yang menentukan injeksi dependensi terdaftar di konstruktor kelas, dan kami juga memberi tahu aplikasi tempat menemukan resource REST (dalam kasus Anda, MyResource) menggunakan packages()panggilan metode.


1
Bagaimana dengan EntityManager? Ada petunjuk bagaimana cara mengikatnya, jadi saya bisa menyuntikkannya melalui @PersistenceContext?
Johannes Staehlin

4
Saya tidak yakin apa EntityManageritu, tapi dilihat dari docs.oracle.com/javaee/6/api/javax/persistence/… sepertinya itu sebuah antarmuka. Anda dapat mengikat menggunakan bind(EntityManagerImpl.class).to(EntityManager.class)(yang akan mengikat kelas EntityManagerImplmengimplementasikan antarmuka EntityManager. Jika Anda perlu menggunakan sebuah pabrik, kita lihat bindFactory()di AbstractBinder. Jika Anda memerlukan bantuan dengan ini, silakan membuat pertanyaan baru (saya tidak akan memiliki ruang untuk jawab di komentar). Selain itu, saya tidak yakin Anda harus menggunakan @PersistentContext, cukup gunakan @Inject untuk semuanya
joscarsson

Ya, EntityManager adalah khusus JPA (Java EE). Terima kasih atas komentar Anda, saya akan membuka pertanyaan lain jika saya mengalami masalah tertentu!
Johannes Staehlin

Sekadar catatan, JPA juga berjalan di Java SE. oracle.com/technetwork/java/javaee/tech/…
prefabSOFT

2
Apa yang dilakukan bind? Bagaimana jika saya memiliki antarmuka dan implementasi?
Dejell

52

Pertama, baru menjawab komentar di bagian penerima jawaban.

"Apa yang dilakukan bind? Bagaimana jika saya memiliki antarmuka dan implementasi?"

Itu hanya berbunyi bind( implementation ).to( contract ). Anda bisa rantai alternatif .in( scope ). Cakupan default PerLookup. Jadi jika Anda menginginkan seorang lajang, Anda bisa

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

Ada juga yang RequestScopedtersedia

Selain itu, alih-alih bind(Class).to(Class), Anda juga bisa bind(Instance).to(Class), yang secara otomatis akan menjadi tunggal.


Menambah jawaban yang diterima

Bagi mereka yang mencoba mencari cara untuk mendaftarkan AbstractBinderimplementasi Anda di web.xml Anda (yaitu Anda tidak menggunakan a ResourceConfig), tampaknya pengikat tidak akan ditemukan melalui pemindaian paket, yaitu

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

Atau ini juga

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

Untuk membuatnya berfungsi, saya harus menerapkan 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;
    }
}

The @Providerpenjelasan harus memungkinkan Featureuntuk dijemput oleh scanning paket. Atau tanpa pemindaian paket, Anda dapat mendaftar secara eksplisit Featurediweb.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>

Lihat juga:

dan untuk informasi umum dari dokumentasi Jersey


MEMPERBARUI

Pabrik

Selain pengikatan dasar dalam jawaban yang diterima, Anda juga memiliki pabrik, tempat Anda dapat memiliki logika pembuatan yang lebih kompleks, dan juga memiliki akses untuk meminta informasi konteks. Sebagai contoh

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

Kemudian Anda dapat menyuntikkan MyServiceke kelas sumber daya Anda.


Saya bisa mendaftarkan kelas pengikat saya hanya melalui implementasi ResourceConfig, seperti yang ditunjukkan dalam jawaban yang diterima. Tidak ada kelas Fitur yang dibutuhkan.
Patrick Koorevaar

Menggunakan web.xmlmeskipun configure()on Hk2Featuredipanggil, meminta resource akan menampilkan file NullPointerException. @PaulSamsotha
bytesandcaffeine

12

Jawaban yang dipilih berasal dari beberapa waktu yang lalu. Tidaklah praktis untuk mendeklarasikan setiap pengikatan dalam pengikat HK2 kustom. Saya menggunakan Tomcat dan saya hanya perlu menambahkan satu ketergantungan. Meskipun dirancang untuk Glassfish, ia sangat cocok dengan wadah lain.

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

Pastikan penampung Anda juga dikonfigurasi dengan benar ( lihat dokumentasi ).


Baris terakhir (Pastikan penampung Anda juga dikonfigurasi dengan benar) agak kabur. Ada bantuan di sini? Anotasi apa yang kami gunakan di mana?
markthegrea

Kami menggunakan Weld untuk injeksi ketergantungan yang membutuhkan beberapa konfigurasi khusus untuk bekerja dengan Tomcat ("wadah" aplikasi kami). Jika Anda menggunakan Spring, itu berfungsi di luar kotak.
otonglet

5

Terlambat tapi saya harap ini membantu seseorang.

Saya memiliki JAX RS saya yang didefinisikan seperti ini:

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

Kemudian, dalam kode saya akhirnya saya bisa menyuntikkan:

@Inject
SomeManagedBean bean;

Dalam kasus saya, itu SomeManagedBeanadalah kacang ApplicationScoped.

Semoga ini bisa membantu siapa pun.


3

Oracle merekomendasikan untuk menambahkan anotasi @Path ke semua jenis untuk dimasukkan saat menggabungkan JAX-RS dengan CDI: http://docs.oracle.com/javaee/7/tutorial/jaxrs-advanced004.htm Meskipun ini jauh dari sempurna ( misalnya Anda akan mendapat peringatan dari Jersey saat memulai), saya memutuskan untuk mengambil rute ini, yang menyelamatkan saya dari mempertahankan semua jenis yang didukung dalam pengikat.

Contoh:

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

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

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

1
Link sudah mati, harus menunjuk ke sini
Hank


0

Bagi saya ini berfungsi tanpa AbstractBinderjika saya menyertakan dependensi berikut dalam aplikasi web saya (berjalan di 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>

Ia bekerja dengan CDI 1.2 / CDI 2.0 untuk saya (masing-masing menggunakan Weld 2/3).


0

Ketergantungan diperlukan untuk layanan jersey restful dan Tomcat adalah servernya. dengan $ {jersey.version} adalah 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>

Kode dasarnya adalah sebagai berikut:

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

   @GET
   public String getMessage() {
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.