Einführung in Spring Remoting mit HTTP-Invokern

Einführung in Spring Remoting mit HTTP-Invokern

1. Überblick

In einigen Fällen müssen wir ein System in mehrere Prozesse aufteilen, die jeweils die Verantwortung für einen anderen Aspekt unserer Anwendung übernehmen. In diesen Szenarien ist es nicht ungewöhnlich, dass einer der Prozesse synchron Daten von einem anderen abrufen muss.

Das Spring Framework bietet eine Reihe von Tools, die umfassend alsSpring Remoting bezeichnet werden und es uns ermöglichen, Remotedienste so aufzurufen, als wären sie zumindest teilweise lokal verfügbar.

In diesem Artikel richten wir eine Anwendung ein, die aufHTTP invokervon Spring basiert und die native Java-Serialisierung und HTTP nutzt, um einen Remote-Methodenaufruf zwischen einem Client und einer Serveranwendung bereitzustellen.

2. Service-Definition

Nehmen wir an, wir müssen ein System implementieren, mit dem Benutzer eine Fahrt in einem Taxi buchen können.

Nehmen wir auch an, wir bauentwo distinct applications, um dieses Ziel zu erreichen:

  • eine Buchungsmaschinenanwendung, um zu prüfen, ob eine Taxianforderung bedient werden kann, und

  • Eine Front-End-Webanwendung, mit der Kunden ihre Fahrten buchen können, um sicherzustellen, dass die Verfügbarkeit eines Taxis bestätigt wurde

2.1. Serviceschnittstelle

Wenn wirSpring Remoting mitHTTP invoker, verwenden, müssen wir unseren remote aufrufbaren Service über eine Schnittstelle definieren, damit Spring sowohl auf Client- als auch auf Serverseite Proxys erstellen kann, die die technischen Details des Remote-Aufrufs kapseln. Beginnen wir also mit der Schnittstelle eines Dienstes, mit dem wir ein Taxi buchen können:

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

Wenn der Dienst ein Taxi zuweisen kann, gibt er einBooking-Objekt mit einem Reservierungscode zurück. Booking muss serialisierbar sein, da der HTTP-Aufrufer von Spring seine Instanzen vom Server auf den Client übertragen muss:

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
}

Wenn der Dienst kein Taxi buchen kann, wird einBookingException ausgelöst. In diesem Fall muss die Klasse nicht alsSerializable markiert werden, daException sie bereits implementiert:

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

2.2. Service verpacken

Die Serviceschnittstelle muss zusammen mit allen benutzerdefinierten Klassen, die als Argumente, Rückgabetypen und Ausnahmen verwendet werden, sowohl im Klassenpfad des Clients als auch des Servers verfügbar sein. Eine der effektivsten Möglichkeiten, dies zu tun, besteht darin, alle in eine.jar-Datei zu packen, die später als Abhängigkeit in diepom.xml von Server und Client aufgenommen werden kann.

Lassen Sie uns also den gesamten Code in ein dediziertes Maven-Modul namens "api" einfügen. In diesem Beispiel werden die folgenden Maven-Koordinaten verwendet:

com.example
api
1.0-SNAPSHOT

3. Serveranwendung

Erstellen wir die Booking Engine-Anwendung, um den Service mithilfe von Spring Boot verfügbar zu machen.

3.1. Maven-Abhängigkeiten

Zunächst müssen Sie sicherstellen, dass Ihr Projekt Spring Boot verwendet:


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

Sie finden die letzte Spring Boot-Versionhere. Wir brauchen dann das Web Starter Modul:


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

Und wir benötigen das Service-Definitionsmodul, das wir im vorherigen Schritt zusammengestellt haben:


    com.example
    api
    1.0-SNAPSHOT

3.2. Service-Implementierung

Zunächst definieren wir eine Klasse, die die Schnittstelle des Dienstes implementiert:

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

Stellen wir uns vor, dies sei eine wahrscheinliche Implementierung. Mithilfe eines Tests mit einem zufälligen Wert können wir sowohl erfolgreiche Szenarien - wenn ein verfügbares Fahrerhaus gefunden und ein Reservierungscode zurückgegeben wurde - als auch fehlgeschlagene Szenarien reproduzieren, wenn eine BookingException ausgelöst wird, um anzuzeigen, dass kein Fahrerhaus verfügbar ist.

3.3. Den Service verfügbar machen

Wir müssen dann eine Anwendung mit einer Bean vom TypHttpInvokerServiceExporter im Kontext definieren. Es wird dafür gesorgt, dass ein HTTP-Einstiegspunkt in der Webanwendung verfügbar gemacht wird, der später vom Client aufgerufen wird:

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

Es ist erwähnenswert, dass Spring'sHTTP invoker den Namen derHttpInvokerServiceExporter Bean als relativen Pfad für die HTTP-Endpunkt-URL verwendet.

Jetzt können wir die Serveranwendung starten und weiter ausführen, während wir die Clientanwendung einrichten.

4. Client-Anwendung

Schreiben wir nun die Client-Anwendung.

4.1. Maven-Abhängigkeiten

Wir verwenden dieselbe Service-Definition und dieselbe Spring Boot-Version, die wir auf der Serverseite verwendet haben. Wir benötigen weiterhin die Webstarter-Abhängigkeit, aber da wir einen eingebetteten Container nicht automatisch starten müssen, können wir den Tomcat-Starter von der Abhängigkeit ausschließen:


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

4.2. Client-Implementierung

Implementieren wir den Client:

@Configuration
public class Client {

    @Bean
    public HttpInvokerProxyFactoryBean invoker() {
        HttpInvokerProxyFactoryBean invoker = new HttpInvokerProxyFactoryBean();
        invoker.setServiceUrl("http://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"));
    }
}

Die mit@Bean kommentierte Methode()des Aufrufers erstellt eine Instanz vonHttpInvokerProxyFactoryBean. Wir müssen die URL angeben, auf die der Remote-Server über diesetServiceUrl()-Methode antwortet.

Ähnlich wie bei dem Server sollten wir auch die Schnittstelle des Dienstes bereitstellen, den wir über die MethodesetServiceInterface()remote aufrufen möchten.

HttpInvokerProxyFactoryBean implementiert Spring’sFactoryBean. EinFactoryBean ist als Bean definiert, aber der Spring IoC-Container injiziert das von ihm erstellte Objekt und nicht die Factory selbst. Weitere Details zuFactoryBean finden Sie in unserenfactory bean article.

Diemain()-Methode bootet die eigenständige Anwendung und ruft eine Instanz vonCabBookingService aus dem Kontext ab. Unter der Haube ist dieses Objekt nur ein vonHttpInvokerProxyFactoryBean erstellter Proxy, der sich um alle technischen Details kümmert, die bei der Ausführung des Remote-Aufrufs erforderlich sind. Dank dessen können wir den Proxy jetzt ganz einfach so verwenden, als ob die Service-Implementierung lokal verfügbar gewesen wäre.

Führen Sie die Anwendung mehrmals aus, um mehrere Remote-Aufrufe auszuführen und zu überprüfen, wie sich der Client verhält, wenn eine Kabine verfügbar ist und wenn dies nicht der Fall ist.

5. Vorbehalt Emptor

Wenn wir mit Technologien arbeiten, die Remoteaufrufe ermöglichen, sollten wir uns einiger Fallstricke bewusst sein.

Wir sollten immer das Unerwartete erwarten, wenn wir mit einer unzuverlässigen Ressource als Netzwerk arbeiten.

Angenommen, der Client ruft den Server auf, während er nicht erreichbar ist - entweder aufgrund eines Netzwerkproblems oder weil der Server nicht verfügbar ist. Dann löst Spring RemotingRemoteAccessException aus, dh_ a _RuntimeException.

Der Compiler wird uns dann nicht zwingen, den Aufruf in einen Try-Catch-Block aufzunehmen, aber wir sollten immer darüber nachdenken, dies zu tun, um Netzwerkprobleme ordnungsgemäß zu verwalten.

5.2. Objekte werden nach Wert und nicht nach Referenz übertragen

Spring Remoting HTTP stellt Methodenargumente zur Verfügung und gibt Werte zurück, um sie im Netzwerk zu übertragen. Dies bedeutet, dass der Server auf eine Kopie des angegebenen Arguments und der Client auf eine Kopie des vom Server erstellten Ergebnisses reagiert.

So können wir beispielsweise nicht erwarten, dass das Aufrufen einer Methode für das resultierende Objekt den Status desselben Objekts auf der Serverseite ändert, da zwischen Client und Server kein gemeinsames Objekt vorhanden ist.

5.3. Vorsicht vor feinkörnigen Schnittstellen

Das Aufrufen einer Methode über Netzwerkgrenzen hinweg ist erheblich langsamer als das Aufrufen für ein Objekt im selben Prozess.

Aus diesem Grund ist es in der Regel empfehlenswert, Dienste zu definieren, die remote mit gröberen Schnittstellen aufgerufen werden sollen, um Geschäftsvorgänge abzuschließen, für die weniger Interaktionen erforderlich sind, selbst auf Kosten einer umständlicheren Schnittstelle.

6. Fazit

In diesem Beispiel haben wir gesehen, wie einfach es mit Spring Remoting ist, einen Remote-Prozess aufzurufen.

Die Lösung ist etwas weniger offen als andere weit verbreitete Mechanismen wie REST oder Webservices, kann jedoch in Szenarien, in denen alle Komponenten mit Spring entwickelt werden, eine praktikable und weitaus schnellere Alternative darstellen.

Wie üblich finden Sie die Quellenover on GitHub.