Spring Cloud Stream事件路由 - spring.io

banq發表於2019-11-07

Spring Cloud Stream(SCSt)的事件路由有以下功能:a)將事件路由到特定事件訂閱者,或b)將事件訂閱者產生的事件路由到特定目的地。
讓我們快速看一下基於註釋的程式設計模型中的工作方式。在本文中,我們將其稱為路由“ TO”和路由“ FROM”。
為了路由到事件訂閱者,我們使用condition了StreamListener註釋的屬性,如下所示:

@StreamListener(target = Sink.INPUT, condition = "headers['type']=='order'")
public void receiveOrders(Order order) {...}


是有關此方法的更多詳細資訊。
而且,為了從事件訂閱者進行路由,我們使用了動態繫結目標 -這種方法允許框架根據單個事件中提供的某些指令將框架繫結到目標。

具有函式的事件路由
使用函式性方法,我們可以透過一些附加函式以更簡潔明瞭的方式完成上述所有操作。

路由“ TO”
可以透過依賴Spring Cloud Function(SCF)中可用的路由功能來實現路由“ TO”功能。您可以透過設定spring.cloud.stream.function.routing.enabled屬性來顯式啟用路由,也可以透過設定spring.cloud.function.routing-expression屬性並使用Spring Expression Language(SpEL)提供路由指令來隱式啟用路由。路由指令應導致路由到“ TO”的功能的定義。
對於路由目的,路由目的地的名稱是functionRouter-in-0(見RoutingFunction.FUNCTION_NAME和描述的繫結命名約定在這裡)。
當一個訊息被髮送到該目的地,路由功能嘗試確定哪些實際功能需要來處理這樣的事件。它首先試圖訪問spring.cloud.function.routing-expression訊息報頭,並且如果提供,確定實際的函式呼叫的名稱。這是最動態的方法。第二種最動態的方法是提供spring.cloud.function.definition標頭,其中應包含將“ TO”路由到的函式的定義。兩種方法都需要透過設定spring.cloud.stream.function.routing.enabled屬性來明確啟用路由功能。
至於以前版本中沒有的其他功能,spring.cloud.function.routing-expression也可以用作應用程式屬性。例如,請考慮無論傳入事件如何,表示式都相同的情況,如本文前面顯示的基於註釋的示例(例如,spring.cloud.function.routing-expression=headers['type']=='order')。對於這種方法,您無需顯式啟用路由功能,因為spring.cloud.function.routing-expression作為應用程式屬性具有相同的效果。
儘管很簡單,但以下是上述方法之一的完整示例:

@SpringBootApplication
public class RoutingStreamApplication {

  public static void main(String[] args) {
      SpringApplication.run(RoutingStreamApplication.class,
      "--spring.cloud.function.routing-expression="
      + "T(java.lang.System).nanoTime() % 2 == 0 ? 'even' : 'odd'");
  }
  @Bean
  public Consumer<Integer> even() {
    return value -> System.out.println("EVEN: " + value);
  }

  @Bean
  public Consumer<Integer> odd() {
    return value -> System.out.println("ODD: " + value);
  }
}


透過傳送訊息到functionRouter-in-0,這是由rabbit或kafka繫結暴露的,基於訊息系統時間的nanoTime()方法返回值,訊息將被路由到Consumer相應的'even'或'odd'

路由“ FROM”
和以前一樣,路由“ FROM”依賴於SCSt的“動態繫結目標”功能。但是,與路由“ TO”一樣,還有許多其他功能。
以下示例顯示了基礎知識:

@Autowired
private BinderAwareChannelResolver resolver;

public Consumer<String> send(Message message) {   
     MessageChannel destination = resolver
        .resolveDestination(message.getHeaders().get("type"))
     Message outgoingMessage = . . . // your code
     destination.send(outgoingMessage);
}


您所需要的只是BinderAwareChannelResolver的引用(在後面的示例中自動注入)。然後,您可以使用一些邏輯來確定目標名稱(在本例中,我們使用“型別”標頭的值)。確定目的地名稱後,您可以透過使用該BinderAwareChannelResolver.resolveDestination(..)操作並向其傳送訊息來獲取對其的引用。這就是全部。
上述方法的缺點是某些特定於框架的抽象會洩漏到您的程式碼中。看一下您需要了解BinderAwareChannelResolver和的MessageChannel事實。實際上,前面示例中的大多數程式碼都是樣板程式碼。
一種更動態,更少洩漏的方法是依靠spring.cloud.stream.sendto.destination屬性,這有效地完成了上述所有操作-但在幕後。下面的示例演示如何使用此方法:

@SpringBootApplication
public class RoutingStreamApplication {

  @Bean
  public Function<Message<String>, Message<String>> process() {
    return message -> {
      // some logic to process incoming message
      Message<String> outgoingMessage = MessageBuilder
        .withPayload("Hello")
        .setHeader("spring.cloud.stream.sendto.destination", "even")
        .build();
       return outgoingMessage;
     };
  }
}

我們不再需要注入BinderAwareChannelResolver執行解析MessageChannel。我們只需建立一個新Message,指定一個頭部header:框架使用這個標頭即可動態解析目標。

路由源
最後但並非最不重要的一點,讓我們看一下路由“ FROM”的另一個流行用例,其中資料來源起源於SCSt的上下文之外,但需要路由到適當的目的地:

@Controller
public class SourceWithDynamicDestination {
    @Autowired
    private ObjectMapper jsonMapper;

    private final EmitterProcessor<?> processor = EmitterProcessor.create();

    @RequestMapping(path = "/", method = POST, consumes = "*/*")
    @ResponseStatus(HttpStatus.ACCEPTED)
    public void handleRequest(@RequestBody String body, 
      @RequestHeader(HttpHeaders.CONTENT_TYPE) Object contentType) 
      throws Exception {
        Map<String, String> payload = jsonMapper.readValue(body, Map.class);
        String destination = payload.get("id");
        Message<?> message =
          MessageBuilder.withPayload(payload)
           .setHeader("spring.cloud.stream.sendto.destination", destination)
           .build();
        processor.onNext(message);
    }

    @Bean
    public Supplier<Flux<?>> source() {
        return () -> processor;
    }
}

然後,我們可以透過執行以下curl命令來檢視結果:

curl -H "Content-Type: application/json" -X POST -d '{"id":"customerId-1","bill-pay":"100"}' http://localhost:8080


在這裡,我們藉助Supplier<Flux<?>>bean 既使用函式性方法又使用反應式範例的混合。我們有一個簡單的MVC控制器,我們希望根據內容的'id'屬性值將請求路由到下游。儘管EmitterProcessor此處的詳細資訊及其用法是另一篇文章的主題,但重要的是它演示了一個功能齊全的應用程式,其中HTTP請求被動態路由到目標繫結程式管理的目的地。
在GitHub上檢視Spring Cloud Stream
 

相關文章