Introduktion til fjernfjernbetjening med HTTP-invokere

1. Oversigt

I nogle tilfælde er vi nødt til at nedbryde et system i flere processer, der hver især tager ansvar for et andet aspekt af vores applikation. I disse scenarier er det ikke ualmindeligt, at en af ​​processerne har brug for synkront at hente data fra en anden.

Spring Framework tilbyder en række værktøjer, der omfattende kaldes Fjederfjernelse der giver os mulighed for at påberåbe sig fjerntjenester, som om de i det mindste til en vis grad var tilgængelige lokalt.

I denne artikel opretter vi en applikation baseret på Spring's HTTP-invoker, der udnytter native Java-serialisering og HTTP til at give fjernmetodeopkald mellem en klient og en serverapplikation.

2. Service Definition

Lad os antage, at vi er nødt til at implementere et system, der giver brugerne mulighed for at booke en tur i en taxa.

Lad os også antage, at vi vælger at bygge to forskellige applikationer for at opnå dette mål:

  • en applikationsmotorapplikation for at kontrollere, om en cab-anmodning kan serveres, og
  • en front-end webapplikation, der giver kunderne mulighed for at booke deres forlystelser og sikre, at tilgængeligheden af ​​en førerhus er blevet bekræftet

2.1. Serviceinterface

Når vi bruger Fjederfjernelse med HTTP-invoker, vi er nødt til at definere vores fjernopkaldelige service gennem en grænseflade for at lade Spring oprette proxyer på både klient- og serversiden, der indkapsler det tekniske ved fjernopkaldet. Så lad os starte med grænsefladen til en tjeneste, der giver os mulighed for at bestille en taxa:

offentlig grænseflade CabBookingService {Booking bookRide (String pickUpLocation) kaster BookingException; }

Når tjenesten er i stand til at tildele en kabine, returnerer den en Booking objekt med en reservationskode. Booking skal være serierbar, fordi Spring's HTTP-invoker skal overføre sine forekomster fra serveren til klienten:

public class Booking implementerer Serializable {private String bookingCode; @ Override public String toString () {returformat ("Ride bekræftet: kode '% s'.", BookingCode); } // standard getters / setters og en konstruktør}

Hvis tjenesten ikke er i stand til at reservere en taxa, skal en BookingUndtagelse kastes. I dette tilfælde er der ingen grund til at markere klassen som Serialiserbar fordi Undtagelse implementerer det allerede:

offentlig klasse BookingException udvider undtagelse {public BookingException (streng besked) {super (besked); }}

2.2. Emballering af tjenesten

Tjenestegrænsefladen sammen med alle brugerdefinerede klasser, der bruges som argumenter, returtyper og undtagelser skal være tilgængelige både på klientens og serverens klassesti. En af de mest effektive måder at gøre det på er at pakke dem alle sammen i en .krukke fil, der senere kan inkluderes som en afhængighed i serverens og klientens pom.xml.

Lad os således sætte al koden i et dedikeret Maven-modul, kaldet “api”; vi bruger følgende Maven-koordinater til dette eksempel:

com.baeldung api 1.0-SNAPSHOT

3. Serverapplikation

Lad os bygge bookingmotorapplikationen for at udsætte tjenesten ved hjælp af Spring Boot.

3.1. Maven afhængigheder

Først skal du sørge for, at dit projekt bruger Spring Boot:

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

Du kan finde den sidste Spring Boot-version her. Vi har derefter brug for webstartermodulet:

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

Og vi har brug for servicedefinitionsmodulet, som vi samlede i det forrige trin:

 com.baeldung api 1.0-SNAPSHOT 

3.2. Serviceimplementering

Vi definerer først en klasse, der implementerer tjenestens grænseflade:

offentlig klasse CabBookingServiceImpl implementerer CabBookingService {@Override public Booking bookPickUp (String pickUpLocation) kaster BookingException {hvis (tilfældig () <0,3) smider nyt BookingException ("Førerhus utilgængelig"); returner ny reservation (randomUUID (). tilString ()); }}

Lad os foregive, at dette er en sandsynlig implementering. Ved hjælp af en test med en tilfældig værdi kan vi gengive både vellykkede scenarier - når en tilgængelig kabine er fundet og en reservationskode returneret - og svigtende scenarier - når en BookingException kastes for at indikere, at der ikke er nogen ledig kabine.

3.3. Eksponering af tjenesten

Vi skal derefter definere en applikation med en bønne af typen HttpInvokerServiceExporter i sammenhængen. Det tager sig af at eksponere et HTTP-indgangspunkt i webapplikationen, som senere påberåbes af klienten:

@Configuration @ComponentScan @EnableAutoConfiguration public class Server {@Bean (name = "/ booking") HttpInvokerServiceExporter accountService () {HttpInvokerServiceExporter eksportør = ny HttpInvokerServiceExporter (); eksportør.setService (ny CabBookingServiceImpl ()); eksportør.setServiceInterface (CabBookingService.class); retureksportør; } offentlig statisk ugyldig hoved (String [] args) {SpringApplication.run (Server.class, args); }}

Det er værd at bemærke, at foråret er HTTP-invoker bruger navnet på HttpInvokerServiceExporter bønne som en relativ sti til HTTP-slutpunkts-URL'en.

Vi kan nu starte serverapplikationen og holde den kørende, mens vi konfigurerer klientapplikationen.

4. Kundeapplikation

Lad os nu skrive klientapplikationen.

4.1. Maven afhængigheder

Vi bruger den samme servicedefinition og den samme Spring Boot-version, som vi brugte på serversiden. Vi har stadig brug for webstarterafhængighed, men da vi ikke behøver automatisk at starte en integreret container, kan vi udelukke Tomcat-starter fra afhængigheden:

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

4.2. Klientimplementering

Lad os implementere klienten:

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

Det @Bønne annoteret invoker() metoden opretter en forekomst af HttpInvokerProxyFactoryBean. Vi er nødt til at angive den URL, som den eksterne server reagerer på gennem setServiceUrl () metode.

På samme måde som hvad vi gjorde for serveren, skal vi også levere grænsefladen til den service, vi vil påberåbe eksternt gennem setServiceInterface () metode.

HttpInvokerProxyFactoryBean implementerer forårets FactoryBean. EN FactoryBean er defineret som en bønne, men Spring IoC-beholderen injicerer det objekt, den opretter, ikke selve fabrikken. Du kan finde flere detaljer om FactoryBean i vores fabriksbønneartikel.

Det hoved () metode bootstrapper den enkeltstående applikation og opnår en forekomst af CabBookingService fra sammenhængen. Under emhætten er dette objekt kun en proxy oprettet af HttpInvokerProxyFactoryBean der tager sig af alle tekniske forhold, der er involveret i udførelsen af ​​fjernanvendelsen. Takket være det kan vi nu let bruge proxyen, som vi ville gøre, hvis serviceimplementeringen havde været tilgængelig lokalt.

Lad os køre applikationen flere gange for at udføre flere fjernopkald for at kontrollere, hvordan klienten opfører sig, når en kabine er tilgængelig, og når den ikke er.

5. Advarselstom

Når vi arbejder med teknologier, der tillader fjernopkald, er der nogle faldgruber, som vi bør være opmærksomme på.

5.1. Pas på netværksrelaterede undtagelser

Vi bør altid forvente det uventede, når vi arbejder med en upålidelig ressource som netværket.

Lad os antage, at klienten påkalder serveren, mens den ikke kan nås - enten på grund af et netværksproblem eller fordi serveren er nede - så hæver Spring Remoting en RemoteAccessException det er en RuntimeException.

Compileren vil ikke derefter tvinge os til at inkludere påkaldelsen i en prøvefangst-blok, men vi bør altid overveje at gøre det for at administrere netværksproblemer korrekt.

5.2. Objekter overføres efter værdi, ikke ved reference

HTTP til fjederfjernelse marshals metode argumenter og returnerede værdier for at transmittere dem på netværket. Dette betyder, at serveren handler på en kopi af det medfølgende argument, og klienten handler på en kopi af det resultat, der er oprettet af serveren.

Så vi kan for eksempel ikke forvente, at påkald af en metode på det resulterende objekt vil ændre status for det samme objekt på serversiden, fordi der ikke er noget delt objekt mellem klient og server.

5.3. Pas på finkornede grænseflader

At påkalde en metode på tværs af netværksgrænser er betydeligt langsommere end at påkalde den på et objekt i samme proces.

Af denne grund er det normalt en god praksis at definere tjenester, der skal påberåbes eksternt med grovere kornede grænseflader, der er i stand til at gennemføre forretningstransaktioner, der kræver færre interaktioner, selv på bekostning af en mere besværlig grænseflade.

6. Konklusion

Med dette eksempel så vi, hvordan det er nemt med Spring Remoting at påberåbe sig en fjernproces.

Løsningen er lidt mindre åben end andre udbredte mekanismer som REST eller webtjenester, men i scenarier, hvor alle komponenter er udviklet med Spring, kan det repræsentere et levedygtigt og langt hurtigere alternativ.

Som sædvanligt finder du kilderne på GitHub.