Въведение в пролетното отдалечаване с HTTP Invokers

1. Общ преглед

В някои случаи трябва да разложим системата на няколко процеса, като всеки поема отговорност за различен аспект на нашето приложение. В тези сценарии не е необичайно, че един от процесите трябва да получава синхронно данни от друг.

Spring Framework предлага набор от инструменти, изцяло наречени Spring Remoting, които ни позволяват да извикаме отдалечени услуги, сякаш поне до известна степен са достъпни на местно ниво.

В тази статия ще създадем приложение, базирано на HTTP извикващото устройство на Spring , което използва собствена Java сериализация и HTTP, за да осигури отдалечено извикване на метод между клиент и сървърно приложение.

2. Определение на услугата

Да предположим, че трябва да внедрим система, която позволява на потребителите да резервират пътуване в такси.

Да предположим също, че сме избрали да изградим две отделни приложения, за да постигнем тази цел:

  • заявление за двигател за резервации, за да се провери дали заявка за такси може да бъде връчена, и
  • уеб приложение от преден край, което позволява на клиентите да резервират своите пътувания, гарантирайки наличието на кабина е потвърдено

2.1. Сервизен интерфейс

Когато използваме Spring Remoting с HTTP invoker, трябва да дефинираме нашата дистанционно извикваща се услуга чрез интерфейс, за да позволим на Spring да създава прокси както от страна на клиента, така и от сървъра, които капсулират техническите характеристики на отдалеченото повикване. И така, нека започнем с интерфейса на услуга, която ни позволява да резервираме такси:

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

Когато услугата е в състояние да се разпределят такси, тя връща Резервация обект с предварителна резервация код. Резервацията трябва да бъде сериализуема, тъй като HTTP призоваващото устройство на Spring трябва да прехвърли своите екземпляри от сървъра на клиента:

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 }

Ако услугата не е в състояние да резервира такси, се изхвърля BookingException . В този случай няма нужда да маркирате класа като сериализуем, защото Exception вече го прилага:

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

2.2. Опаковане на услугата

Интерфейсът на услугата, заедно с всички потребителски класове, използвани като аргументи, типове връщане и изключения, трябва да бъдат налични както в пътя на класа на клиента, така и в сървъра. Един от най-ефективните начини за това е да се опаковат всички в .jar файл, който по-късно може да бъде включен като зависимост в pom.xml на сървъра и клиента .

По този начин нека поставим целия код в специален модул Maven, наречен „api“; ще използваме следните координати на Maven за този пример:

com.baeldung api 1.0-SNAPSHOT

3. Сървърно приложение

Нека да изградим приложението на резервационния механизъм, за да изложим услугата с помощта на Spring Boot.

3.1. Зависимости на Maven

Първо, трябва да се уверите, че вашият проект използва Spring Boot:

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

Можете да намерите последната версия на Spring Boot тук. След това се нуждаем от модула за уеб стартиране:

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

И се нуждаем от модула за дефиниция на услугата, който сме събрали в предишната стъпка:

 com.baeldung api 1.0-SNAPSHOT 

3.2. Внедряване на услугата

Първо дефинираме клас, който реализира интерфейса на услугата:

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

Нека се преструваме, че това е вероятно изпълнение. Използвайки тест с произволна стойност, ще можем да възпроизведем и двата успешни сценария - когато е намерена налична кабина и върнат код за резервация - и неуспешни сценарии - когато е хвърлен BookingException, за да покаже, че няма налична кабина.

3.3. Излагане на услугата

След това трябва да дефинираме приложение с боб от тип HttpInvokerServiceExporter в контекста. Той ще се погрижи за излагане на HTTP входна точка в уеб приложението, която по-късно ще бъде извикана от клиента:

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

Заслужава да се отбележи, че HTTP извиквачът на Spring използва името на боб HttpInvokerServiceExporter като относителен път за HTTP URL адреса на крайната точка.

Вече можем да стартираме сървърното приложение и да го поддържаме, докато настройваме клиентското приложение.

4. Приложение на клиента

Нека сега напишем клиентското приложение.

4.1. Зависимости на Maven

Ще използваме същата дефиниция на услугата и същата версия на Spring Boot, която използвахме от страна на сървъра. Все още се нуждаем от зависимостта на уеб стартера, но тъй като не е необходимо автоматично да стартираме вграден контейнер, можем да изключим стартера Tomcat от зависимостта:

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

4.2. Внедряване на клиента

Нека внедрим клиента:

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

Решението е малко по-малко отворено от други широко разпространени механизми като REST или уеб услуги, но в сценарии, при които всички компоненти са разработени с Spring, то може да представлява жизнеспособна и далеч по-бърза алтернатива.

Както обикновено, ще намерите източниците в GitHub.