Verwenden von Subflows bei der Spring-Integration

Verwenden von Unterabläufen in der Frühlingsintegration

1. Überblick

Spring Integration macht es einfach, einigeEnterprise Integration Patterns zu verwenden. Eine dieser Möglichkeiten ist durchits DSL.

In diesem Tutorial werden wir uns die Unterstützung von DSL für Subflows ansehen, um einige unserer Konfigurationen zu vereinfachen.

2. Unsere Aufgabe

Nehmen wir an, wir haben eine Folge von Ganzzahlen, die wir in drei verschiedene Buckets aufteilen möchten.

Wenn wir dazu Spring Integration verwenden möchten, können wir zunächst drei Ausgabekanäle erstellen:

  • Zahlen wie 0, 3, 6 und 9 gehen zumultipleOfThreeChannel

  • Zahlen wie 1, 4, 7 und 10 gehen anremainderIsOneChannel

  • Und Zahlen wie 2, 5, 8 und 11 gehen zuremainderIsTwoChannel

Um zu sehen, wie hilfreich Subflows sein können, beginnen wir damit, wie dies ohne Subflows aussehen wird.

Anschließend verwenden wir Unterabläufe, um unsere Konfiguration zu vereinfachen:

  • PublishSubscribeChannel

  • routeToRecipients

  • Filters, um die Logik vonif-thenzu konfigurieren

  • Routers, um die Logik vonswitchzu konfigurieren

3. Voraussetzungen

Bevor wir unsere Unterabläufe konfigurieren, erstellen wir diese Ausgabekanäle.

Wir werden dieseQueueChannels erstellen, da dies etwas einfacher zu demonstrieren ist:

@EnableIntegration
@IntegrationComponentScan
public class SubflowsConfiguration {

    @Bean
    QueueChannel multipleOfThreeChannel() {
        return new QueueChannel();
    }

    @Bean
    QueueChannel remainderIsOneChannel() {
        return new QueueChannel();
    }

    @Bean
    QueueChannel remainderIsTwoChannel() {
        return new QueueChannel();
    }

    boolean isMultipleOfThree(Integer number) {
       return number % 3 == 0;
    }

    boolean isRemainderIOne(Integer number) {
        return number % 3 == 1;
    }

    boolean isRemainderTwo(Integer number) {
        return number % 3 == 2;
    }
}

Letztendlich werden hier unsere gruppierten Zahlen landen.

Beachten Sie auch, dass Spring Integration leicht komplex aussehen kann. Aus Gründen der Lesbarkeit werden wir daher einige Hilfsmethoden hinzufügen.

4. Lösen ohne Subflows

Jetzt müssen wir unsere Abläufe definieren.

Ohne Unterabläufe besteht die einfache Idee darin, drei separate Integrationsabläufe zu definieren, einen für jeden Zahlentyp.

Wir senden dieselbe Folge von Nachrichten an jedeIntegrationFlow-Komponente, aber die Ausgabenachrichten für jede Komponente sind unterschiedlich.

4.1. IntegrationFlow Komponenten definieren

Definieren wir zunächst jedeIntegrationFlow-Bohne in unsererSubflowConfiguration -Skala:

@Bean
public IntegrationFlow multipleOfThreeFlow() {
    return flow -> flow.split()
      . filter(this::isMultipleOfThree)
      .channel("multipleOfThreeChannel");
}

Unser Flow enthält zwei Endpunkte - einSplitter gefolgt von einemFilter.

Der Filter macht, wie es sich anhört. Aber warum brauchen wir auch einen Splitter? Wir werden dies in einer Minute sehen, aber im Grunde wird eine EingabeCollection in einzelne Nachrichten aufgeteilt.

Und wir können natürlich zwei weitereIntegrationFlow Bohnen auf die gleiche Weise definieren.

4.2. Messaging-Gateways

Für jeden Fluss benötigen wir auch einMessage Gateway.

Einfach ausgedrückt, diese abstrahieren die Spring Integration Messages-API vom Aufrufer weg, ähnlich wie ein REST-Service HTTP abstrahieren kann:

@MessagingGateway
public interface NumbersClassifier {

    @Gateway(requestChannel = "multipleOfThreeFlow.input")
    void multipleOfThree(Collection numbers);

    @Gateway(requestChannel = "remainderIsOneFlow.input")
    void remainderIsOne(Collection numbers);

    @Gateway(requestChannel = "remainderIsTwoFlow.input")
    void remainderIsTwo(Collection numbers);

}

Für jeden müssen wir die@Gateway -Sannotation verwenden und den impliziten Namen für den Eingangskanal angeben, der einfach der Name der Bean ist, gefolgt von“.input”. Note that we can use this convention because we are using lambda-based flows.

Diese Methoden sind die Einstiegspunkte in unsere Abläufe.

4.3. Senden von Nachrichten und Überprüfen der Ausgabe

Und jetzt testen wir:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { SeparateFlowsConfiguration.class })
public class SeparateFlowsUnitTest {

    @Autowired
    private QueueChannel multipleOfThreeChannel;

    @Autowired
    private NumbersClassifier numbersClassifier;
    @Test
    public void whenSendMessagesToMultipleOf3Flow_thenOutputMultiplesOf3() {
        numbersClassifier.multipleOfThree(Arrays.asList(1, 2, 3, 4, 5, 6));
        Message outMessage = multipleOfThreeChannel.receive(0);
        assertEquals(outMessage.getPayload(), 3);
        outMessage = multipleOfThreeChannel.receive(0);
        assertEquals(outMessage.getPayload(), 6);
        outMessage = multipleOfThreeChannel.receive(0);
        assertNull(outMessage);
    }
}

Beachten Sie, dass wir die Nachrichten alsList gesendet haben. Deshalb brauchten wir den Splitter, um die einzelne "Listennachricht" in mehrere "Nummernnachrichten" umzuwandeln.

Wir rufenreceive mito auf, um die nächste verfügbare Nachricht zu erhalten, ohne zu warten. Da unsere Liste zwei Vielfache von drei enthält, können wir davon ausgehen, dass wir sie zweimal aufrufen können. Der dritte Aufruf vonreceive gibtnull zurück.

receive, of Kurs, gibt einMessage zurück, also rufen wirgetPayload auf, um die Zahl zu extrahieren.

Ebenso könnten wir das gleiche für die beiden anderen tun.

So, that was the solution without subflows. Wir müssen drei separate Flows verwalten und drei separate Gateway-Methoden.

Was wir jetzt tun werden, ist, die dreiIntegrationFlow beans durch eine einzelne Bean und die drei Gateway-Methoden durch eine einzelne zu ersetzen.

5. Verwenden vonpublishSubscribeChannel

Die MethodepublishSubscribeChannel()endet Nachrichten an alle abonnierenden Unterflüsse. Auf diese Weise können wir einen Flow anstelle von drei erstellen.

@Bean
public IntegrationFlow classify() {
    return flow -> flow.split()
        .publishSubscribeChannel(subscription ->
           subscription
             .subscribe(subflow -> subflow
               . filter(this::isMultipleOfThree)
               .channel("multipleOfThreeChannel"))
             .subscribe(subflow -> subflow
                . filter(this::isRemainderOne)
                .channel("remainderIsOneChannel"))
             .subscribe(subflow -> subflow
                . filter(this::isRemainderTwo)
                .channel("remainderIsTwoChannel")));
}

Auf diese Weise wirdthe subflows are anonymous, meaning that they can’t be independently addressed.

Jetzt haben wir nur noch einen Flow. Bearbeiten wir also unsereNumbersClassifier as gut:

@Gateway(requestChannel = "classify.input")
void classify(Collection numbers);

Nunsince we have only one IntegrationFlow bean and one gateway method, we need only send our list once:

@Test
public void whenSendMessagesToFlow_thenNumbersAreClassified() {
    numbersClassifier.classify(Arrays.asList(1, 2, 3, 4, 5, 6));

    // same assertions as before
}

Beachten Sie, dass sich von nun an nur noch die Definition des Integrationsflusses ändert, sodass der Test nicht mehr angezeigt wird.

6. Verwenden vonrouteToRecipients

Eine andere Möglichkeit, dasselbe zu erreichen, istrouteToRecipients, was gut ist, da eine Filterung integriert ist.

Mit dieser Methode wirdwe can specify both channels and subflows for broadcasting. 

6.1. recipient

Im folgenden Code geben wirmultipleof3Channel,remainderIs1Channel, undremainderIsTwoChannel als Empfänger an, basierend auf unseren Bedingungen:

@Bean
public IntegrationFlow classify() {
    return flow -> flow.split()
        .routeToRecipients(route -> route
          . recipient("multipleOfThreeChannel",
            this::isMultipleOfThree)
          . recipient("remainderIsOneChannel",
            this::isRemainderOne)
          . recipient("remainderIsTwoChannel",
            this::isRemainderTwo));
}

Wir können auchrecipient ohne Bedingung aufrufen undrouteToRecipients bedingungslos an diesem Ziel veröffentlichen.

6.2. recipientFlow

Und beachten Sie, dassrouteToRecipients es uns ermöglicht, einen vollständigen Fluss zu definieren, genau wiepublishSubscribeChannel. 

Ändern Sie den obigen Code und geben Sie einanonymous subflow as the first recipientan:

.routeToRecipients(route -> route
  .recipientFlow(subflow -> subflow
      . filter(this::isMultipleOfThree)
      .channel("mutipleOfThreeChannel"))
  ...);

This subflow will receive the entire sequence of messages,, also müssen wir wie zuvor filtern, um das gleiche Verhalten zu erhalten.

Wieder reichte uns eineIntegrationFlow Bohne.

Fahren wir nun mit den Komponenten vonif-elsefort. Einer von ihnen istFilter.

7. Verwenden vonif-then Flows

In allen vorherigen Beispielen haben wir bereitsFilter verwendet. Die gute Nachricht ist, dass wir nicht nur die Bedingung für die weitere Verarbeitung angeben können, sondern aucha channel or aflow for the discarded messages.

Wir können uns Flows und Kanäle wie einenelse block vorstellen:

@Bean
public IntegrationFlow classify() {
    return flow -> flow.split()
        . filter(this::isMultipleOfThree,
           notMultiple -> notMultiple
             .discardFlow(oneflow -> oneflow
               . filter(this::isRemainderOne,
                 twoflow -> twoflow
                   .discardChannel("remainderIsTwoChannel"))
               .channel("remainderIsOneChannel"))
        .channel("multipleofThreeChannel");
}

In diesem Fall haben wir die Routing-Logik vonif-elseimplementiert:

  • If die Zahl ist kein Vielfaches von drei,then verwirft diese Nachrichten an den Verwerfungsfluss; we use a flow here since there is more logic needed to know its destination channel.

  • Im Verwerfungsfluss,if ist die Anzahl nicht der Rest eins,then verwirft diese Nachrichten an den Verwerfungskanal.

8. switch-ingen auf einem berechneten Wert

Und zum Schluss versuchen wir dieroute -Smethodik, die uns etwas mehr Kontrolle gibt alsrouteToRecipients. . Es ist schön, weil einRouter den Fluss in eine beliebige Anzahl von Teilen aufteilen kann, während einFilter -Scan mach nur zwei.

8.1. channelMapping

Definieren wir unsereIntegrationFlow Bean:

@Bean
public IntegrationFlow classify() {
    return classify -> classify.split()
      . route(number -> number % 3,
        mapping -> mapping
         .channelMapping(0, "multipleOfThreeChannel")
         .channelMapping(1, "remainderIsOneChannel")
         .channelMapping(2, "remainderIsTwoChannel"));
}

Im obigen Code berechnen wir einen Routing-Schlüssel durch Ausführen der Division:

route(p -> p % 3,...

Basierend auf diesem Schlüssel leiten wir die Nachrichten weiter:

channelMapping(0, "multipleof3Channel")

8.2. subFlowMapping

Jetzt können wir wie bei anderen mehr Kontrolle übernehmen, indem wir einen Unterfluss angeben undchannelMapping durchsubFlowMapping ersetzen:

.subFlowMapping(1, subflow -> subflow.channel("remainderIsOneChannel"))

Oder noch mehr Kontrolle durch Aufrufen derhandle -Smethod anstelle derchannel -Smethod:

.subFlowMapping(2, subflow -> subflow
  . handle((payload, headers) -> {
      // do extra work on the payload
     return payload;
  }))).channel("remainderIsTwoChannel");

In diesem Fall würde der Unterfluss nach der Methoderoute() zum Hauptfluss zurückkehren. Daher müssten wir dort den KanalremainderIsTwoChannel. angeben

9. Fazit

In diesem Tutorial haben wir untersucht, wie Nachrichten mithilfe von Subflows auf bestimmte Weise gefiltert und weitergeleitet werden.

Wie üblich ist der vollständige Quellcodeon GitHub verfügbar.