Pengenalan Pemasaran Ulang Musim Semi dengan Invokers HTTP

1. Gambaran keseluruhan

Dalam beberapa kes, kita perlu menguraikan sistem ke dalam beberapa proses, masing-masing bertanggungjawab untuk aspek aplikasi kita yang berbeza. Dalam senario ini tidak jarang salah satu proses perlu mendapatkan data dari yang lain secara serentak.

Spring Framework menawarkan pelbagai alat secara komprehensif yang disebut Spring Remoting yang memungkinkan kita untuk menggunakan perkhidmatan jarak jauh seolah-olah mereka, sekurang-kurangnya sampai tahap tertentu, tersedia di dalam negara.

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

2. Definisi Perkhidmatan

Anggaplah kita harus melaksanakan sistem yang membolehkan pengguna menempah perjalanan di teksi.

Mari kita anggap juga bahawa kita memilih untuk membina dua aplikasi yang berbeza untuk mencapai tujuan ini:

  • aplikasi enjin tempahan untuk memeriksa sama ada permintaan teksi dapat diserahkan, dan
  • aplikasi web bahagian depan yang membolehkan pelanggan menempah perjalanan mereka, memastikan ketersediaan teksi telah disahkan

2.1. Antaramuka Perkhidmatan

Apabila kita menggunakan Spring Remoting dengan HTTP invoker , kita harus menentukan perkhidmatan yang boleh dipanggil dari jarak jauh melalui antara muka untuk membiarkan Spring membuat proksi di kedua-dua pelanggan dan pelayan yang merangkumi teknikal panggilan jarak jauh. Oleh itu, mari kita mulakan dengan antara muka perkhidmatan yang membolehkan kita menempah teksi:

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

Apabila perkhidmatan itu mampu untuk memperuntukkan teksi, ia mengembalikan Tempahan objek dengan kod tempahan. Pemesanan harus dibuat secara bersiri kerana penyerang HTTP Spring harus memindahkan contohnya dari pelayan ke pelanggan:

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 }

Sekiranya perkhidmatan tidak dapat menempah teksi, maka BookingException akan dilemparkan. Dalam kes ini, tidak perlu menandakan kelas sebagai Serializable kerana Pengecualian sudah menerapkannya:

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

2.2. Mengemas Perkhidmatan

Antaramuka perkhidmatan bersama dengan semua kelas tersuai yang digunakan sebagai argumen, jenis pengembalian dan pengecualian harus tersedia di classpath pelanggan dan pelayan. Salah satu cara yang paling berkesan untuk melakukannya adalah dengan memasukkan semuanya ke dalam fail .jar yang kemudian dapat dimasukkan sebagai ketergantungan dalam pom.xml pelayan dan klien .

Oleh itu, mari masukkan semua kod dalam modul Maven khusus, yang disebut "api"; kami akan menggunakan koordinat Maven berikut untuk contoh ini:

com.baeldung api 1.0-SNAPSHOT

3. Aplikasi Pelayan

Mari bina aplikasi enjin tempahan untuk mendedahkan perkhidmatan menggunakan Spring Boot.

3.1. Ketergantungan Maven

Pertama, anda perlu memastikan projek anda menggunakan Spring Boot:

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

Anda boleh mendapatkan versi Spring Boot terakhir di sini. Kami kemudian memerlukan modul pemula Web:

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

Dan kami memerlukan modul definisi perkhidmatan yang kami kumpulkan pada langkah sebelumnya:

 com.baeldung api 1.0-SNAPSHOT 

3.2. Pelaksanaan Perkhidmatan

Mula-mula kami menentukan kelas yang menerapkan antara muka perkhidmatan:

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 berpura-pura bahawa ini adalah kemungkinan pelaksanaan. Dengan menggunakan ujian dengan nilai rawak, kami akan dapat menghasilkan semula kedua-dua senario yang berjaya - apabila kabin yang ada telah dijumpai dan kod tempahan dikembalikan - dan senario yang gagal - apabila BookingException dilemparkan untuk menunjukkan bahawa tidak ada teksi yang tersedia.

3.3. Mendedahkan Perkhidmatan

Kita kemudian perlu menentukan aplikasi dengan kacang jenis HttpInvokerServiceExporter dalam konteksnya. Ini akan menguruskan untuk memaparkan titik masuk HTTP dalam aplikasi web yang kemudiannya akan dipanggil oleh pelanggan:

@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 diingat bahawa penyerang HTTP Spring menggunakan nama kacang HttpInvokerServiceExporter sebagai jalan relatif untuk URL titik akhir HTTP.

Kita sekarang boleh memulakan aplikasi pelayan dan terus berjalan semasa kita menyiapkan aplikasi klien.

4. Permohonan Pelanggan

Mari sekarang tulis aplikasi pelanggan.

4.1. Ketergantungan Maven

Kami akan menggunakan definisi perkhidmatan yang sama dan versi Spring Boot yang sama dengan yang kami gunakan di bahagian pelayan. Kami masih memerlukan pergantungan pemula web, tetapi kerana kami tidak perlu memulakan wadah tertanam secara automatik, kami dapat mengecualikan pemula Tomcat dari ketergantungan:

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

4.2. Pelaksanaan Pelanggan

Mari laksanakan 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.

Penyelesaiannya agak kurang terbuka daripada mekanisme lain yang meluas seperti REST atau perkhidmatan web, tetapi dalam senario di mana semua komponen dikembangkan dengan Spring, ia dapat mewakili alternatif yang dapat dilaksanakan dan jauh lebih cepat.

Seperti biasa, anda akan menemui sumbernya di GitHub.