Spring AMQP in reaktiven Anwendungen

Spring AMQP in reaktiven Anwendungen

1. Überblick

Dieses Tutorial zeigt, wie eine einfache Spring Boot Reactive-Anwendung erstellt wird, die in den RabbitMQ-Nachrichtenserver integriert wird, eine beliebte Implementierung des AMQP-Nachrichtenstandards.

Wir behandeln beide Szenarien - Punkt-zu-Punkt- und Publish-Subscribe-Szenarien - mithilfe eines verteilten Setups, das die Unterschiede zwischen beiden Mustern hervorhebt.

Beachten Sie, dass wir Grundkenntnisse in AMQP, RabbitMQ und Spring Boot voraussetzen, insbesondere Schlüsselkonzepte wie Börsen, Warteschlangen, Themen usw. Weitere Informationen zu diesen Konzepten finden Sie unter den folgenden Links:

2. RabbitMQ Server Setup

Obwohl wir ein lokales RabbitMQ lokal einrichten könnten, verwenden wir in der Praxis eher eine dedizierte Installation mit zusätzlichen Funktionen wie Hochverfügbarkeit, Überwachung, Sicherheit usw.

Um eine solche Umgebung in unserer Entwicklungsmaschine zu simulieren, verwenden wir Docker, um einen Server zu erstellen, den unsere Anwendung verwenden wird.

Der folgende Befehl startet einen eigenständigen RabbitMQ-Server:

$ docker run -d --name rabbitmq -p 5672:5672 rabbitmq:3

Wir deklarieren kein dauerhaftes Volume, sodass ungelesene Nachrichten zwischen Neustarts verloren gehen. Der Dienst wird auf dem Host an Port 5672 verfügbar sein.

Wir können Serverprotokolle mit dem Befehldocker logs überprüfen, der eine Ausgabe wie die folgende erzeugen sollte:

$ docker logs rabbitmq
2018-06-09 13:42:29.718 [info] <0.33.0>
  Application lager started on node [email protected]
// ... some lines omitted
2018-06-09 13:42:33.491 [info] <0.226.0>
 Starting RabbitMQ 3.7.5 on Erlang 20.3.5
 Copyright (C) 2007-2018 Pivotal Software, Inc.
 Licensed under the MPL.  See http://www.rabbitmq.com/

  ##  ##
  ##  ##      RabbitMQ 3.7.5. Copyright (C) 2007-2018 Pivotal Software, Inc.
  ##########  Licensed under the MPL.  See http://www.rabbitmq.com/
  ######  ##
  ##########  Logs: 

              Starting broker...
2018-06-09 13:42:33.494 [info] <0.226.0>
 node           : [email protected]
 home dir       : /var/lib/rabbitmq
 config file(s) : /etc/rabbitmq/rabbitmq.conf
 cookie hash    : CY9rzUYh03PK3k6DJie09g==
 log(s)         : 
 database dir   : /var/lib/rabbitmq/mnesia/[email protected]

// ... more log lines

Da das Image das Dienstprogrammrabbitmqctlenthält, können wir es verwenden, um Verwaltungsaufgaben im Kontext unseres laufenden Images auszuführen.

Beispielsweise können wir Serverstatusinformationen mit dem folgenden Befehl abrufen:

$ docker exec rabbitmq rabbitmqctl status
Status of node [email protected] ...
[{pid,299},
 {running_applications,
     [{rabbit,"RabbitMQ","3.7.5"},
      {rabbit_common,
          "Modules shared by rabbitmq-server and rabbitmq-erlang-client",
          "3.7.5"},
// ... other info omitted for brevity

Andere nützliche Befehle sind:

  • list_exchanges: Listet alle deklarierten Börsen auf

  • list_queues: Listet alle deklarierten Warteschlangen auf, einschließlich der Anzahl der ungelesenen Nachrichten

  • list_bindings: Listet alle definierten Bindungen zwischen Vermittlungsstellen und Warteschlangen auf, einschließlich Routing-Schlüsseln

3. Spring AMQP Project Setup

Sobald wir unseren RabbitMQ-Server eingerichtet haben, können wir mit der Erstellung unseres Spring-Projekts fortfahren. Mit diesem Beispielprojekt kann jeder REST-Client mithilfe des Spring AMQP-Moduls und des entsprechenden Spring Boot-Starters Nachrichten an den Nachrichtenserver senden und / oder empfangen, um mit ihm zu kommunizieren.

Die wichtigsten Abhängigkeiten, die wir zur Projektdatei vonpom.xmlhinzufügen müssen, sind:


    org.springframework.boot
    spring-boot-starter-amqp
    2.0.3.RELEASE


    org.springframework.boot
    spring-boot-starter-webflux
    2.0.2.RELEASE

spring-boot-starter-amqp enthält alle AMQP-bezogenen Daten, währendspring-boot-starter-webflux die Kernabhängigkeit ist, die zur Implementierung unseres reaktiven REST-Servers verwendet wird.

Hinweis: Sie können die neueste Version der Module Spring Boot StarterAMQP undWebflux in Maven Central überprüfen.

4. Szenario 1: Punkt-zu-Punkt-Messaging

In diesem ersten Szenario verwenden wir Direct Exchange, die logische Entität im Broker, die Nachrichten von Clients empfängt.

A Direct Exchange will route all incoming messages to one – and only one – queue, von denen es für Kunden zum Verbrauch verfügbar sein wird. Mehrere Clients können dieselbe Warteschlange abonnieren, aber nur einer erhält eine bestimmte Nachricht.

4.1. Einrichtung von Exchange und Warteschlangen

In unserem Szenario verwenden wir einDestinationInfo-Objekt, das den Austauschnamen und den Routing-Schlüssel kapselt. Eine Karte mit dem Namen des Ziels speichert alle verfügbaren Ziele.

Die folgende@PostConstruct method ist für diese Ersteinrichtung verantwortlich:

@Autowired
private AmqpAdmin amqpAdmin;

@Autowired
private DestinationsConfig destinationsConfig;

@PostConstruct
public void setupQueueDestinations() {
    destinationsConfig.getQueues()
        .forEach((key, destination) -> {
            Exchange ex = ExchangeBuilder.directExchange(
              destination.getExchange())
              .durable(true)
              .build();
            amqpAdmin.declareExchange(ex);
            Queue q = QueueBuilder.durable(
              destination.getRoutingKey())
              .build();
            amqpAdmin.declareQueue(q);
            Binding b = BindingBuilder.bind(q)
              .to(ex)
              .with(destination.getRoutingKey())
              .noargs();
            amqpAdmin.declareBinding(b);
        });
}

Diese Methode verwendet die von Spring erstellteadminAmqp -Sbean, um Börsen und Warteschlangen zu deklarieren und sie mithilfe eines bestimmten Routing-Schlüssels miteinander zu verbinden.

Alle Ziele stammen aus einerDestinationsConfig bean, einer in unserem Beispiel verwendeten@ConfigurationProperties-Klasse.

Diese Klasse verfügt über eine Eigenschaft, die mitDestinationInfo-Objekten gefüllt ist, die aus Zuordnungen erstellt wurden, die aus der Konfigurationsdatei vonapplication.ymlgelesen wurden.

4.2. Producer-Endpunkt

Produzenten senden Nachrichten, indem sie einHTTP POST an den Standort von/queue/{name}enden.

Dies ist ein reaktiver Endpunkt, daher verwenden wirMono, um eine einfache Bestätigung zurückzugeben:

@SpringBootApplication
@EnableConfigurationProperties(DestinationsConfig.class)
@RestController
public class SpringWebfluxAmqpApplication {

    // ... other members omitted

    @Autowired
    private AmqpTemplate amqpTemplate;

    @PostMapping(value = "/queue/{name}")
    public Mono> sendMessageToQueue(
      @PathVariable String name, @RequestBody String payload) {

        DestinationInfo d = destinationsConfig
          .getQueues().get(name);
        if (d == null) {
            return Mono.just(
              ResponseEntity.notFound().build());
        }

        return Mono.fromCallable(() -> {
            amqpTemplate.convertAndSend(
              d.getExchange(),
              d.getRoutingKey(),
              payload);
            return ResponseEntity.accepted().build();
        });
    }

Wir prüfen zunächst, ob der Parameter name einem gültigen Ziel entspricht, und verwenden in diesem Fall die Instanz von autowiredamqpTemplate, um die Nutzdaten - eine einfache Nachricht vonString- tatsächlich an RabbitMQ zu senden.

4.3. MessageListenerContainer Fabrik

Um Nachrichten asynchron zu empfangen, verwendet Spring AMQP die abstrakte KlasseMessageContainerListener, die den Informationsfluss von AMQP-Warteschlangen und Listenern vermittelt, die von einer Anwendung bereitgestellt werden.

Da wir eine konkrete Implementierung dieser Klasse benötigen, um unsere Nachrichten-Listener anzuhängen, definieren wir eine Factory, die den Controller-Code von seiner tatsächlichen Implementierung isoliert.

In unserem Fall gibt die Factory-Methode jedes Mal ein neuesSimpleMessageContainerListener zurück, wenn wir diecreateMessageListenerContainer-Methode aufrufen:

@Component
public class MessageListenerContainerFactory {

    @Autowired
    private ConnectionFactory connectionFactory;

    public MessageListenerContainerFactory() {}

    public MessageListenerContainer createMessageListenerContainer(String queueName) {
        SimpleMessageListenerContainer mlc = new SimpleMessageListenerContainer(connectionFactory);
        mlc.addQueueNames(queueName);
        return mlc;
    }
}

4.4. Consumer-Endpunkt

Verbraucher greifen auf dieselbe Endpunktadresse zu, die von Herstellern (/queue/{name}) zum Abrufen von Nachrichten verwendet wird.

Dieser Endpunkt gibt einFlux of-Ereignis zurück, wobei jedes Ereignis einer empfangenen Nachricht entspricht:

@Autowired
private MessageListenerContainerFactory messageListenerContainerFactory;

@GetMapping(
  value = "/queue/{name}",
  produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux receiveMessagesFromQueue(@PathVariable String name) {

    DestinationInfo d = destinationsConfig
      .getQueues()
      .get(name);
    if (d == null) {
        return Flux.just(ResponseEntity.notFound()
          .build());
    }

    MessageListenerContainer mlc = messageListenerContainerFactory
      .createMessageListenerContainer(d.getRoutingKey());

    Flux f = Flux. create(emitter -> {
        mlc.setupMessageListener((MessageListener) m -> {
            String payload = new String(m.getBody());
            emitter.next(payload);
        });
        emitter.onRequest(v -> {
            mlc.start();
        });
        emitter.onDispose(() -> {
            mlc.stop();
        });
      });

    return Flux.interval(Duration.ofSeconds(5))
      .map(v -> "No news is good news")
      .mergeWith(f);
}

Nach der ersten Überprüfung des Zielnamens erstellt der Endpunkt des VerbrauchersMessageListenerContainer unter Verwendung vonMessageListenerContainerFactory und des aus unserer Registrierung wiederhergestellten Warteschlangennamens.

Sobald wir unsereMessageListenerContainer haben, erstellen wir die NachrichtFlux mit einer der Builder-Methoden voncreate().

In unserem speziellen Fall verwenden wir eines, das ein Lambda mit einemFluxSink-Argument verwendet, das wir dann verwenden, um die Listener-basierte asynchrone API von Spring AMQP mit unserer reaktiven Anwendung zu verbinden.

Wir fügen außerdem zwei zusätzliche Lambdas zu den RückrufenonRequest() undonDispose()des Emitters hinzu, damit unserMessageListenerContainer -Scan seine internen Ressourcen nach dem Lebenszyklus vonFluxzuweist / freigibt.

Schließlich führen wir das resultierendeFlux mit einem anderen zusammen, das mitinterval(), erstellt wurde und das alle fünf Sekunden ein neues Ereignis erstellt. Those dummy messages play an important function in our case: Ohne sie erkennen wir eine Client-Trennung erst, wenn eine Nachricht empfangen und nicht gesendet wird. Dies kann je nach Anwendungsfall sehr lange dauern.

4.5. Testen

Mit der Einrichtung sowohl unserer Consumer- als auch Publisher-Endpunkte können wir nun einige Tests mit unserer Beispielanwendung durchführen.

Wir müssen die Serververbindungsdetails von RabbitMQ und mindestens ein Ziel auf unserenapplication.ymldefinieren, die folgendermaßen aussehen sollten:

spring:
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest

destinations:
  queues:
    NYSE:
      exchange: nyse
      routing-key: NYSE

Die Eigenschaften vonspring.rabbitmq.*definieren die grundlegenden Eigenschaften, die für die Verbindung mit unserem RabbitMQ-Server erforderlich sind, der in einem lokalen Docker-Container ausgeführt wird. Bitte beachten Sie, dass die oben gezeigte IP nur ein Beispiel ist und in einem bestimmten Setup möglicherweise anders ist.

Warteschlangen werden mitdestinations.queues.<name>.* definiert, wobei<name> als Zielname verwendet wird. Hier haben wir ein einzelnes Ziel namens "NYSE" deklariert, das mit einem "NYSE" -Routing-Schlüssel Nachrichten an die "nyse" -Austauschstelle auf RabbitMQ sendet.

Sobald wir den Server über die Befehlszeile oder von unserer IDE aus starten, können wir Nachrichten senden und empfangen. Wir verwenden das Dienstprogrammcurl, ein allgemeines Dienstprogramm, das sowohl für Windows-, Mac- als auch Linux-Betriebssysteme verfügbar ist.

Die folgende Auflistung zeigt, wie Sie eine Nachricht an unser Ziel und die erwartete Antwort vom Server senden:

$ curl -v -d "Test message" http://localhost:8080/queue/NYSE
* timeout on name lookup is not supported
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> POST /queue/NYSE HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.49.1
> Accept: */*
> Content-Length: 12
> Content-Type: application/x-www-form-urlencoded
>
* upload completely sent off: 12 out of 12 bytes
< HTTP/1.1 202 Accepted
< content-length: 0
<
* Connection #0 to host localhost left intact

Nachdem wir diesen Befehl ausgeführt haben, können wir überprüfen, ob die Nachricht von RabbitMQ empfangen wurde und zum Verzehr bereit ist, indem wir den folgenden Befehl eingeben:

$ docker exec rabbitmq rabbitmqctl list_queues
Timeout: 60.0 seconds ...
Listing queues for vhost / ...
NYSE    1

Jetzt können wir Nachrichten mit dem folgenden Befehl lesen:

$ curl -v http://localhost:8080/queue/NYSE
* timeout on name lookup is not supported
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /queue/NYSE HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.49.1
> Accept: */*
>
< HTTP/1.1 200 OK
< transfer-encoding: chunked
< Content-Type: text/event-stream;charset=UTF-8
<
data:Test message

data:No news is good news...

... same message repeating every 5 secs

Wie wir sehen können, erhalten wir zuerst die zuvor gespeicherte Nachricht und beginnen dann, alle 5 Sekunden unsere Dummy-Nachricht zu empfangen.

Wenn wir den Befehl zum Auflisten von Warteschlangen erneut ausführen, sehen wir jetzt, dass keine Nachrichten gespeichert sind:

$ docker exec rabbitmq rabbitmqctl list_queues

Timeout: 60.0 seconds ...
Listing queues for vhost / ...
NYSE    0

5. Szenario 2: Publish-Subscribe

Ein weiteres häufiges Szenario für Messaging-Anwendungen ist das Publish-Subscribe-Muster, bei dem eine einzelne Nachricht an mehrere Konsumenten gesendet werden muss.

RabbitMQ bietet zwei Arten von Austausch, die diese Art von Anwendungen unterstützen: Fan-Out und Topic.

Der Hauptunterschied zwischen diesen beiden Arten besteht darin, dass wir anhand eines Routing-Schlüsselmusters (z. "Alarm.mailserver. *") Wird zum Zeitpunkt der Registrierung bereitgestellt, während erstere eingehende Nachrichten einfach in alle gebundenen Warteschlangen replizieren.

RabbitMQ unterstützt auch Header-Austausche, die eine komplexere Nachrichtenfilterung ermöglichen, deren Verwendung jedoch außerhalb des Geltungsbereichs dieses Artikels liegt.

5.1. Ziele einrichten

Wir definieren Pub / Sub-Ziele zum Startzeitpunkt mit einer anderen@PostConstruct -Methode, wie wir es im Punkt-zu-Punkt-Szenario getan haben.

Der einzige Unterschied besteht darin, dass wir nur dieExchanges erstellen, aber keineQueues - diese werden bei Bedarf erstellt und später an dieExchange gebunden, da wir ein exklusivesQueue möchten für jeden Kunden:

@PostConstruct
public void setupTopicDestinations(
    destinationsConfig.getTopics()
      .forEach((key, destination) -> {
          Exchange ex = ExchangeBuilder
            .topicExchange(destination.getExchange())
            .durable(true)
            .build();
            amqpAdmin.declareExchange(ex);
      });
}

5.2. Publisher-Endpunkt

Clients verwenden den Publisher-Endpunkt, der am Standort von/topic/{name}verfügbar ist, um Nachrichten zu veröffentlichen, die an alle verbundenen Clients gesendet werden.

Wie im vorherigen Szenario verwenden wir ein@PostMapping, das nach dem Senden der Nachricht einMono mit dem Status zurückgibt:

@PostMapping(value = "/topic/{name}")
public Mono> sendMessageToTopic(
  @PathVariable String name, @RequestBody String payload) {

    DestinationInfo d = destinationsConfig
      .getTopics()
      .get(name);

    if (d == null) {
        return Mono.just(ResponseEntity.notFound().build());
    }

   return Mono.fromCallable(() -> {
       amqpTemplate.convertAndSend(
         d.getExchange(), d.getRoutingKey(),payload);
            return ResponseEntity.accepted().build();
        });
    }

5.3. Teilnehmer-Endpunkt

Unser Teilnehmerendpunkt befindet sich bei/topic/{name} und erzeugtFlux von Nachrichten für verbundene Clients.

Diese Nachrichten enthalten sowohl die empfangenen Nachrichten als auch die Dummy-Nachrichten, die alle 5 Sekunden generiert werden:

@GetMapping(
  value = "/topic/{name}",
  produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux receiveMessagesFromTopic(@PathVariable String name) {
    DestinationInfo d = destinationsConfig.getTopics()
        .get(name);
    if (d == null) {
        return Flux.just(ResponseEntity.notFound()
            .build());
    }
    Queue topicQueue = createTopicQueue(d);
    String qname = topicQueue.getName();
    MessageListenerContainer mlc = messageListenerContainerFactory.createMessageListenerContainer(qname);
    Flux f = Flux. create(emitter -> {
        mlc.setupMessageListener((MessageListener) m -> {
            String payload = new String(m.getBody());
            emitter.next(payload);
        });
        emitter.onRequest(v -> {
            mlc.start();
        });
        emitter.onDispose(() -> {
            amqpAdmin.deleteQueue(qname);
            mlc.stop();
        });
      });

    return Flux.interval(Duration.ofSeconds(5))
        .map(v -> "No news is good news")
        .mergeWith(f);
}

Dieser Code ist im Grunde derselbe wie im vorherigen Fall, nur mit den folgenden Unterschieden: Zuerst erstellen wir für jeden neuen Abonnenten ein neuesQueue.

Wir tun dies durch einen Aufruf der MethodecreateTopicQueue(), die Informationen aus der InstanzDestinationInfoverwendet, um eine exklusive, nicht dauerhafte Warteschlange zu erstellen, die wir dann mit der konfigurierten Methode anExchangebinden Routing-Schlüssel:

private Queue createTopicQueue(DestinationInfo destination) {

    Exchange ex = ExchangeBuilder
      .topicExchange(destination.getExchange())
      .durable(true)
      .build();
    amqpAdmin.declareExchange(ex);
    Queue q = QueueBuilder
      .nonDurable()
      .build();
    amqpAdmin.declareQueue(q);
    Binding b = BindingBuilder.bind(q)
      .to(ex)
      .with(destination.getRoutingKey())
      .noargs();
    amqpAdmin.declareBinding(b);
    return q;
}

Beachten Sie, dass RabbitMQ trotz der Tatsache, dass wir dieExchange erneut deklarieren, keine neue erstellt, da wir sie bereits beim Start deklariert haben.

Der zweite Unterschied besteht im Lambda, das wir an dieonDispose()-Methode übergeben, die diesmal auch dieQueue löscht, wenn der Teilnehmer die Verbindung trennt.

5.3. Testen

Um das Pub-Sub-Szenario zu testen, müssen wir zuerst ein Themenziel in outapplication.yml wie folgt definieren:

destinations:
## ... queue destinations omitted
  topics:
    weather:
      exchange: alerts
      routing-key: WEATHER

Hier haben wir einen Themenendpunkt definiert, der am Standort von/topic/weatherverfügbar sein wird. Dieser Endpunkt wird zum Posten von Nachrichten an den Warnungsaustausch auf RabbitMQ mit einem WEATHER-Routingschlüssel verwendet.

Nach dem Start des Servers können wir mit dem Befehlrabbitmqctl überprüfen, ob der Austausch erstellt wurde:

$ docker exec docker_rabbitmq_1 rabbitmqctl list_exchanges
Listing exchanges for vhost / ...
amq.topic       topic
amq.fanout      fanout
amq.match       headers
amq.headers     headers
        direct
amq.rabbitmq.trace      topic
amq.direct      direct
alerts  topic

Wenn wir nun den Befehllist_bindings ausgeben, können wir feststellen, dass es keine Warteschlangen im Zusammenhang mit dem Austausch von "Warnungen" gibt:

$ docker exec rabbitmq rabbitmqctl list_bindings
Listing bindings for vhost /...
        exchange        NYSE    queue   NYSE    []
nyse    exchange        NYSE    queue   NYSE    []

Lassen Sie uns ein paar Abonnenten starten, die unser Ziel abonnieren, indem Sie zwei Befehlsshells öffnen und jeweils den folgenden Befehl eingeben:

$ curl -v http://localhost:8080/topic/weather
* timeout on name lookup is not supported
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /topic/weather HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.49.1
> Accept: */*
>
< HTTP/1.1 200 OK
< transfer-encoding: chunked
< Content-Type: text/event-stream;charset=UTF-8
<
data:No news is good news...

# ... same message repeating indefinitely

Schließlich verwenden wir curl erneut, um unseren Abonnenten einige Benachrichtigungen zu senden:

$ curl -v -d "Hurricane approaching!" http://localhost:8080/topic/weather
* timeout on name lookup is not supported
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 8080 (#0)
> POST /topic/weather HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.49.1
> Accept: */*
> Content-Length: 22
> Content-Type: application/x-www-form-urlencoded
>
* upload completely sent off: 22 out of 22 bytes
< HTTP/1.1 202 Accepted
< content-length: 0
<
* Connection #0 to host localhost left intact

Sobald wir die Nachricht gesendet haben, können wir fast sofort die Nachricht "Hurricane approaching!" Auf der Shell jedes Teilnehmers sehen.

Wenn wir jetzt die verfügbaren Bindungen überprüfen, können wir sehen, dass wir eine Warteschlange für jeden Abonnenten haben:

$ docker exec rabbitmq rabbitmqctl list_bindings
Listing bindings for vhost /...
        exchange        IBOV    queue   IBOV    []
        exchange        NYSE    queue   NYSE    []
        exchange        spring.gen-i0m0pbyKQMqpz2_KFZCd0g
  queue   spring.gen-i0m0pbyKQMqpz2_KFZCd0g       []
        exchange        spring.gen-wCHALTsIS1q11PQbARJ7eQ
  queue   spring.gen-wCHALTsIS1q11PQbARJ7eQ       []
alerts  exchange        spring.gen-i0m0pbyKQMqpz2_KFZCd0g
  queue   WEATHER []
alerts  exchange        spring.gen-wCHALTsIS1q11PQbARJ7eQ
  queue   WEATHER []
ibov    exchange        IBOV    queue   IBOV    []
nyse    exchange        NYSE    queue   NYSE    []
quotes  exchange        NYSE    queue   NYSE    []

Sobald wir Strg-C auf der Shell des Abonnenten drücken, erkennt unser Gateway schließlich, dass der Client die Verbindung getrennt hat, und entfernt diese Bindungen.

6. Fazit

In diesem Artikel haben wir gezeigt, wie Sie mit dem Modulspring-amqpeine einfache reaktive Anwendung erstellen, die mit einem RabbitMQ-Server interagiert.

Mit nur wenigen Codezeilen konnten wir ein funktionsfähiges HTTP-zu-AMQP-Gateway erstellen, das sowohl Point-to-Point- als auch Publish-Subscribe-Integrationsmuster unterstützt, die wir problemlos erweitern können, um zusätzliche Funktionen wie die Sicherheit von hinzuzufügen Hinzufügung der Standardfrühlingseigenschaften.

Der in diesem Artikel gezeigte Code istover on Github. verfügbar