Pengantar Spring Remoting dengan HTTP Invokers

1. Ikhtisar

Dalam beberapa kasus, kita perlu menguraikan sistem menjadi beberapa proses, masing-masing bertanggung jawab atas aspek yang berbeda dari aplikasi kita. Dalam skenario ini tidak jarang salah satu proses perlu mendapatkan data secara sinkron dari proses lainnya.

Kerangka Kerja Musim Semi menawarkan berbagai alat yang secara komprehensif disebut Spring Remoting yang memungkinkan kita untuk memanggil layanan jarak jauh seolah-olah mereka, setidaknya sampai batas tertentu, tersedia secara lokal.

Dalam artikel ini, kami akan menyiapkan aplikasi berdasarkan permintaan HTTP Spring , yang memanfaatkan serialisasi Java asli dan HTTP untuk menyediakan pemanggilan metode jarak jauh antara klien dan aplikasi server.

2. Definisi Layanan

Misalkan kita harus menerapkan sistem yang memungkinkan pengguna memesan tumpangan di taksi.

Mari kita juga menganggap bahwa kita memilih untuk membangun dua aplikasi berbeda untuk mencapai tujuan ini:

  • aplikasi mesin pemesanan untuk memeriksa apakah permintaan kabin dapat dilayani, dan
  • aplikasi web front-end yang memungkinkan pelanggan untuk memesan tumpangan mereka, memastikan ketersediaan taksi telah dikonfirmasi

2.1. Antarmuka Layanan

Saat kami menggunakan Spring Remoting dengan HTTP invoker, kami harus menentukan layanan panggilan jarak jauh kami melalui antarmuka untuk memungkinkan Spring membuat proxy di sisi klien dan server yang merangkum teknis panggilan jarak jauh. Jadi, mari kita mulai dengan antarmuka layanan yang memungkinkan kita memesan taksi:

public interface CabBookingService { Booking bookRide(String pickUpLocation) throws BookingException; }

Saat layanan dapat mengalokasikan taksi, layanan mengembalikan objek Booking dengan kode reservasi. Pemesanan harus dapat bersambung karena pemanggil HTTP Spring harus mentransfer instansinya dari server ke klien:

public class Booking implements Serializable { private String bookingCode; @Override public String toString() { return format("Ride confirmed: code '%s'.", bookingCode); } // standard getters/setters and a constructor }

Jika layanan tidak dapat memesan taksi, BookingException akan dilemparkan. Dalam kasus ini, tidak perlu menandai kelas sebagai Dapat Berseri karena Exception sudah mengimplementasikannya:

public class BookingException extends Exception { public BookingException(String message) { super(message); } }

2.2. Mengemas Layanan

Antarmuka layanan bersama dengan semua kelas khusus yang digunakan sebagai argumen, jenis kembalian dan pengecualian harus tersedia di jalur kelas klien dan server. Salah satu cara paling efektif untuk melakukannya adalah dengan mengemas semuanya dalam file .jar yang nantinya dapat disertakan sebagai dependensi di pom.xml server dan klien .

Jadi mari kita letakkan semua kode dalam modul Maven khusus, yang disebut "api"; kami akan menggunakan koordinat Maven berikut untuk contoh ini:

com.baeldung api 1.0-SNAPSHOT

3. Aplikasi Server

Mari buat aplikasi mesin pemesanan untuk mengekspos layanan menggunakan Spring Boot.

3.1. Dependensi Maven

Pertama, Anda harus memastikan proyek Anda menggunakan Spring Boot:

 org.springframework.boot spring-boot-starter-parent 2.2.2.RELEASE 

Anda dapat menemukan versi Spring Boot terakhir di sini. Kami kemudian membutuhkan modul Web starter:

 org.springframework.boot spring-boot-starter-web 

Dan kita membutuhkan modul definisi layanan yang kita rangkai pada langkah sebelumnya:

 com.baeldung api 1.0-SNAPSHOT 

3.2. Implementasi Layanan

Kami pertama-tama mendefinisikan kelas yang mengimplementasikan antarmuka layanan:

public class CabBookingServiceImpl implements CabBookingService { @Override public Booking bookPickUp(String pickUpLocation) throws BookingException { if (random() < 0.3) throw new BookingException("Cab unavailable"); return new Booking(randomUUID().toString()); } }

Mari kita anggap bahwa ini adalah implementasi yang mungkin. Dengan menggunakan pengujian dengan nilai acak, kami akan dapat mereproduksi kedua skenario yang berhasil - saat taksi yang tersedia telah ditemukan dan kode reservasi dikembalikan - dan skenario yang gagal - saat BookingException muncul untuk menunjukkan bahwa tidak ada kabin yang tersedia.

3.3. Mengekspos Layanan

Kita kemudian perlu mendefinisikan aplikasi dengan jenis kacang HttpInvokerServiceExporter dalam konteksnya. Ini akan menangani pemaparan titik entri HTTP di aplikasi web yang nantinya akan dipanggil oleh klien:

@Configuration @ComponentScan @EnableAutoConfiguration public class Server { @Bean(name = "/booking") HttpInvokerServiceExporter accountService() { HttpInvokerServiceExporter exporter = new HttpInvokerServiceExporter(); exporter.setService( new CabBookingServiceImpl() ); exporter.setServiceInterface( CabBookingService.class ); return exporter; } public static void main(String[] args) { SpringApplication.run(Server.class, args); } }

Perlu dicatat bahwa pemanggil HTTP Spring menggunakan nama kacang HttpInvokerServiceExporter sebagai jalur relatif untuk URL titik akhir HTTP.

Sekarang kita dapat memulai aplikasi server dan tetap menjalankannya saat kita menyiapkan aplikasi klien.

4. Aplikasi Klien

Sekarang mari kita tulis aplikasi klien.

4.1. Dependensi Maven

Kami akan menggunakan definisi layanan yang sama dan versi Spring Boot yang sama yang kami gunakan di sisi server. Kami masih membutuhkan ketergantungan web starter, tetapi karena kami tidak perlu secara otomatis memulai penampung yang disematkan, kami dapat mengecualikan starter Tomcat dari ketergantungan:

 org.springframework.boot spring-boot-starter-web   org.springframework.boot spring-boot-starter-tomcat   

4.2. Implementasi Klien

Mari terapkan klien:

@Configuration public class Client { @Bean public HttpInvokerProxyFactoryBean invoker() { HttpInvokerProxyFactoryBean invoker = new HttpInvokerProxyFactoryBean(); invoker.setServiceUrl("//localhost:8080/booking"); invoker.setServiceInterface(CabBookingService.class); return invoker; } public static void main(String[] args) throws BookingException { CabBookingService service = SpringApplication .run(Client.class, args) .getBean(CabBookingService.class); out.println(service.bookRide("13 Seagate Blvd, Key Largo, FL 33037")); } }

The @Bean annotated invoker() method creates an instance of HttpInvokerProxyFactoryBean. We need to provide the URL that the remote server responds at through the setServiceUrl() method.

Similarly to what we did for the server, we should also provide the interface of the service we want to invoke remotely through the setServiceInterface() method.

HttpInvokerProxyFactoryBean implements Spring's FactoryBean. A FactoryBean is defined as a bean, but the Spring IoC container will inject the object it creates, not the factory itself. You can find more details about FactoryBean in our factory bean article.

The main() method bootstraps the stand alone application and obtains an instance of CabBookingService from the context. Under the hood, this object is just a proxy created by the HttpInvokerProxyFactoryBean that takes care of all technicalities involved in the execution of the remote invocation. Thanks to it we can now easily use the proxy as we would do if the service implementation had been available locally.

Let's run the application multiple times to execute several remote calls to verify how the client behaves when a cab is available and when it is not.

5. Caveat Emptor

When we work with technologies that allow remote invocations, there are some pitfalls we should be well aware of.

5.1. Beware of Network Related Exceptions

We should always expect the unexpected when we work with an unreliable resource as the network.

Let's suppose the client is invoking the server while it cannot be reached – either because of a network problem or because the server is down – then Spring Remoting will raise a RemoteAccessException that is a RuntimeException.

The compiler will not then force us to include the invocation in a try-catch block, but we should always consider to do it, to properly manage network problems.

5.2. Objects Are Transferred by Value, Not by Reference

Spring Remoting HTTP marshals method arguments and returned values to transmit them on the network. This means that the server acts upon a copy of the provided argument and the client acts upon a copy of the result created by the server.

So we cannot expect, for instance, that invoking a method on the resulting object will change the status of the same object on the server side because there is not any shared object between client and server.

5.3. Beware of Fine-Grained Interfaces

Invoking a method across network boundaries is significantly slower than invoking it on an object in the same process.

For this reason, it is usually a good practice to define services that should be remotely invoked with coarser grained interfaces that are able to complete business transactions requiring fewer interactions, even at the expense of a more cumbersome interface.

6. Conclusion

With this example, we saw how it is easy with Spring Remoting to invoke a remote process.

Solusinya sedikit kurang terbuka dibandingkan mekanisme luas lainnya seperti REST atau layanan web, tetapi dalam skenario di mana semua komponen dikembangkan dengan Spring, ini dapat mewakili alternatif yang layak dan jauh lebih cepat.

Seperti biasa, Anda akan menemukan sumbernya di GitHub.