Inleiding tot Spring Remoting met HTTP Invokers

1. Overzicht

In sommige gevallen moeten we een systeem opsplitsen in verschillende processen, die elk de verantwoordelijkheid nemen voor een ander aspect van onze applicatie. In deze scenario's is het niet ongebruikelijk dat een van de processen synchroon gegevens uit een ander moet halen.

Het Spring Framework biedt een reeks tools die uitgebreid worden genoemd Lente Remoting dat stelt ons in staat om externe services aan te roepen alsof ze, althans tot op zekere hoogte, lokaal beschikbaar zijn.

In dit artikel gaan we een applicatie opzetten op basis van Spring's HTTP-aanroeper, dat gebruikmaakt van native Java-serialisering en HTTP om externe methode-aanroep tussen een client en een servertoepassing mogelijk te maken.

2. Dienstdefinitie

Stel dat we een systeem moeten implementeren waarmee gebruikers een rit in een taxi kunnen boeken.

Laten we ook aannemen dat we ervoor kiezen om te bouwen twee verschillende toepassingen om dit doel te bereiken:

  • een applicatie voor het boeken van een engine om te controleren of een taxiverzoek kan worden behandeld, en
  • een front-end webapplicatie waarmee klanten hun ritten kunnen boeken, zodat de beschikbaarheid van een taxi is bevestigd

2.1. Service-interface

Wanneer we gebruiken Lente Remoting met HTTP-aanroeper, we moeten onze op afstand oproepbare service definiëren via een interface om Spring proxy's te laten creëren aan zowel client- als serverzijde die de technische details van de externe oproep inkapselen. Laten we dus beginnen met de interface van een dienst waarmee we een taxi kunnen boeken:

openbare interface CabBookingService {Booking bookRide (String pickUpLocation) gooit BookingException; }

Als de service een taxi kan toewijzen, retourneert deze een Boeking object met een reserveringscode. Boeking moet serialiseerbaar zijn omdat de HTTP-aanroeper van Spring zijn instanties van de server naar de client moet overbrengen:

public class Booking implementeert Serializable {private String bookingCode; @Override public String toString () {retourformaat ("Rit bevestigd: code '% s'.", BookingCode); } // standaard getters / setters en een constructor}

Als de dienst geen taxi kan boeken, a BookingException wordt gegooid. In dit geval is het niet nodig om de klasse te markeren als Serialiseerbaar omdat Uitzondering implementeert het al:

public class BookingException breidt Exception uit {public BookingException (String bericht) {super (bericht); }}

2.2. De service verpakken

De service-interface samen met alle aangepaste klassen die als argumenten, retourtypen en uitzonderingen worden gebruikt, moeten beschikbaar zijn in het klassenpad van zowel de client als de server. Een van de meest effectieve manieren om dat te doen, is door ze allemaal in een .pot bestand dat later als afhankelijkheid in het server- en clientbestand kan worden opgenomen pom.xml.

Laten we dus alle code in een speciale Maven-module plaatsen, genaamd "api"; we gebruiken de volgende Maven-coördinaten voor dit voorbeeld:

com.baeldung api 1.0-SNAPSHOT

3. Servertoepassing

Laten we de applicatie voor de boekingsmodule bouwen om de service met Spring Boot beschikbaar te stellen.

3.1. Afhankelijkheden van Maven

Eerst moet u ervoor zorgen dat uw project Spring Boot gebruikt:

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

De laatste Spring Boot-versie vind je hier. We hebben dan de webstartmodule nodig:

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

En we hebben de servicedefinitiemodule nodig die we in de vorige stap hebben samengesteld:

 com.baeldung api 1.0-SNAPSHOT 

3.2. Service-implementatie

We definiëren eerst een klasse die de interface van de service implementeert:

openbare klasse CabBookingServiceImpl implementeert CabBookingService {@Override openbare boeking bookPickUp (String pickUpLocation) gooit BookingException {if (random () <0.3) gooi nieuwe BookingException ("Cab niet beschikbaar"); retourneer nieuwe boeking (randomUUID (). toString ()); }}

Laten we net doen alsof dit een waarschijnlijke implementatie is. Door een test met een willekeurige waarde te gebruiken, kunnen we zowel succesvolle scenario's reproduceren - wanneer een beschikbare taxi is gevonden en een reserveringscode wordt geretourneerd - en falende scenario's - wanneer een BookingException wordt gegenereerd om aan te geven dat er geen beschikbare taxi is.

3.3. De service blootleggen

We moeten dan een applicatie definiëren met een boon van het type HttpInvokerServiceExporter in de context. Het zorgt ervoor dat er een HTTP-toegangspunt in de webtoepassing wordt weergegeven dat later door de client wordt aangeroepen:

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

Het is vermeldenswaard dat Spring's HTTP-aanroeper gebruikt de naam van de HttpInvokerServiceExporter bean als een relatief pad voor de HTTP-eindpunt-URL.

We kunnen nu de servertoepassing starten en laten draaien terwijl we de clienttoepassing opzetten.

4. Clienttoepassing

Laten we nu de clienttoepassing schrijven.

4.1. Afhankelijkheden van Maven

We gebruiken dezelfde servicedefinitie en dezelfde Spring Boot-versie die we aan de serverzijde hebben gebruikt. We hebben nog steeds de afhankelijkheid van de webstarter nodig, maar aangezien we niet automatisch een ingesloten container hoeven te starten, kunnen we de Tomcat-starter uitsluiten van de afhankelijkheid:

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

4.2. Client implementatie

Laten we de klant implementeren:

@Configuratie openbare klasse Client {@Bean openbare HttpInvokerProxyFactoryBean invoker () {HttpInvokerProxyFactoryBean invoker = nieuwe HttpInvokerProxyFactoryBean (); invoker.setServiceUrl ("// localhost: 8080 / boeking"); invoker.setServiceInterface (CabBookingService.class); terugkeer invoker; } public static void main (String [] args) gooit BookingException {CabBookingService service = SpringApplication .run (Client.class, args) .getBean (CabBookingService.class); out.println (service.bookRide ("13 Seagate Blvd, Key Largo, FL 33037")); }}

De @Boon geannoteerde invoker() methode maakt een instantie van HttpInvokerProxyFactoryBean. We moeten de URL opgeven waarop de externe server reageert via het setServiceUrl () methode.

Net als wat we voor de server hebben gedaan, moeten we ook de interface bieden van de service die we op afstand willen aanroepen via de setServiceInterface () methode.

HttpInvokerProxyFactoryBean implementeert Spring's FactoryBean. EEN FactoryBean wordt gedefinieerd als een boon, maar de Spring IoC-container injecteert het object dat het maakt, niet de fabriek zelf. U kunt meer informatie vinden over FactoryBean in ons artikel over fabrieksbonen.

De hoofd() methode bootstraps de stand-alone applicatie en verkrijgt een exemplaar van CabBookingService uit de context. Onder de motorkap is dit object slechts een proxy gemaakt door de HttpInvokerProxyFactoryBean dat zorgt voor alle technische details die betrokken zijn bij de uitvoering van de aanroep op afstand. Dankzij dit kunnen we de proxy nu gemakkelijk gebruiken zoals we zouden doen als de service-implementatie lokaal beschikbaar was geweest.

Laten we de applicatie meerdere keren uitvoeren om verschillende externe oproepen uit te voeren om te controleren hoe de client zich gedraagt ​​wanneer een cab beschikbaar is en wanneer dat niet het geval is.

5. Waarschuwing Emptor

Wanneer we werken met technologieën die aanroepen op afstand mogelijk maken, zijn er enkele valkuilen waarvan we ons terdege bewust moeten zijn.

5.1. Pas op voor netwerkgerelateerde uitzonderingen

We moeten altijd het onverwachte verwachten als we werken met een onbetrouwbare bron als het netwerk.

Stel dat de client de server aanroept terwijl deze niet kan worden bereikt - hetzij vanwege een netwerkprobleem of omdat de server niet beschikbaar is - dan zal Spring Remoting een RemoteAccessException dat is een RuntimeException.

De compiler zal ons dan niet dwingen om de aanroep op te nemen in een try-catch-blok, maar we moeten altijd overwegen om het te doen om netwerkproblemen op de juiste manier te beheren.

5.2. Objecten worden overgedragen op waarde, niet op referentie

Spring Remoting HTTP marshals methode argumenten en geretourneerde waarden om ze over het netwerk te verzenden. Dit betekent dat de server handelt op basis van een kopie van het opgegeven argument en de client handelt op basis van een kopie van het resultaat dat door de server is gemaakt.

We kunnen dus bijvoorbeeld niet verwachten dat het aanroepen van een methode op het resulterende object de status van hetzelfde object aan de serverzijde zal veranderen, omdat er geen gedeeld object is tussen client en server.

5.3. Pas op voor fijnmazige interfaces

Het aanroepen van een methode over netwerkgrenzen heen is aanzienlijk langzamer dan het aanroepen voor een object in hetzelfde proces.

Om deze reden is het meestal een goede gewoonte om services te definiëren die op afstand moeten worden aangeroepen met grovere interfaces die in staat zijn om zakelijke transacties te voltooien die minder interacties vereisen, zelfs ten koste van een omslachtiger interface.

6. Conclusie

Met dit voorbeeld hebben we gezien hoe het met Spring Remoting gemakkelijk is om een ​​extern proces aan te roepen.

De oplossing is iets minder open dan andere wijdverbreide mechanismen zoals REST of webservices, maar in scenario's waarin alle componenten met Spring zijn ontwikkeld, kan het een haalbaar en veel sneller alternatief zijn.

Zoals gewoonlijk vind je de bronnen op GitHub.