Java9异步编程-反应式流使用

开发 后端
在本文中,主要研究下Java9+中的反应流。简单地说,就是使用Flow类,Flow类包含了用于构建响应式流处理的主要模块。

[[438594]]

 在本文中,主要研究下Java9+中的反应流。简单地说,就是使用Flow类,Flow类包含了用于构建响应式流处理的主要模块。

这里说的反应流其实是一种非阻塞式背压的异步流处理标准(有点绕口)。

该规范在响应式宣言中定义,并且有各种各样的实现,例如RxJava或Akka-Streams。

Reactive API总览

要构建一个流,主要使用三个抽象,并将它们组合成异步处理逻辑。

每个流都需要处理由Publisher实例发布给它的事件;发布者有一个subscribe()的方法。

如果某个订阅者希望接收发布者发布的事件,则需要使用subscribe()订阅发布者。

消息的接收方需要实现订阅者接口。一般情况下,接受者是每个Flow处理的结束,因为它的实例不会进一步发送消息。

可以将Subscriber看作Sink。有四个方法需要重写onSubscribe(), onNext(), onError()和onComplete()。

如果希望转换传入的消息并将其进一步传递给下一个订阅服务,则需要实现Processor接口。

它既充当订阅服务(因为它接收消息),又充当发布服务(因为它处理这些消息并将它们发送以进行进一步处理)。

发布和消费消息

假设想要创建一个简单的流,其中有一个发布者发布消息,一个简单的订阅者在消息到达时使用消息。

先创建一个EndSubscriber类。需要实现订阅服务接口。接下来,重写所需的方法。

onSubscribe()方法在处理开始之前被调用。

订阅的实例subscription作为参数传递。Subscription是控制订阅服务和发布服务之间的消息流的类.

  1.  1public class EndSubscriber<T> implements Subscriber<T> { 
  2.  2       // 多少消息需要消费 
  3.  3    private final AtomicInteger howMuchMessagesToConsume; 
  4.  4    private Flow.Subscription subscription; 
  5.  5    // 保存消费过的消息 
  6.  6    public List<T> consumedElements = new LinkedList<>(); 
  7.  7 
  8.  8    public EndSubscriber(Integer howMuchMessagesToConsume) { 
  9.  9        this.howMuchMessagesToConsume = new AtomicInteger(howMuchMessagesToConsume); 
  10. 10    } 
  11. 11 
  12. 12    @Override 
  13. 13    public void onSubscribe(Flow.Subscription subscription) { 
  14. 14        this.subscription = subscription; 
  15. 15        subscription.request(1); 
  16. 16    } 
  17. 17} 

在这里还初始化了一个在测试中使用的消耗元素的空列表。

现在,需要从订阅者接口实现其余的方法。这里的主要方法是onNext(),它在发布者发布新消息时被调用

  1. 1@Override 
  2. 2public void onNext(T item) { 
  3. 3    System.out.println("Got : " + item); 
  4. 4    consumedElements.add(item); 
  5. 5    subscription.request(1); 
  6. 6} 

这里需要注意的的是,当在onSubscribe()方法中启动开始订阅时,以及当处理消息时onNext(),需要调用subscription上的request()方法来通知当前订阅器准备使用更多消息。

最后,需要实现onError(),它会在处理过程中抛出异常时被调用.

在发布者关闭时调用onComplete().

  1. 1@Override 
  2. 2public void onError(Throwable t) { 
  3. 3    t.printStackTrace(); 
  4. 4} 
  5. 6@Override 
  6. 7public void onComplete() { 
  7. 8    System.out.println("Done"); 
  8. 9} 

接下来为这个处理流编写一个测试。将使用SubmissionPublisher类,这是java.util.concurrent中的一个类,它实现了Publisher接口。

测试中向发布者提交N个元素,我们的终端订阅者会接收到这些元素。

  1.  1@Test 
  2.  2public void whenSubscribeToIt_thenShouldConsumeAll()  
  3.  3  throws InterruptedException { 
  4.  4 
  5.  5    // given 
  6.  6    SubmissionPublisher<String> publisher = new SubmissionPublisher<>(); 
  7.  7    EndSubscriber<String> subscriber = new EndSubscriber<>(); 
  8.  8    publisher.subscribe(subscriber); 
  9.  9    List<String> items = List.of("1""x""2""x""3""x"); 
  10. 10 
  11. 11    // when 
  12. 12    assertThat(publisher.getNumberOfSubscribers()).isEqualTo(1); 
  13. 13    items.forEach(publisher::submit); 
  14. 14    publisher.close(); 
  15. 15 
  16. 16    // then 
  17. 17     await().atMost(1000, TimeUnit.MILLISECONDS) 
  18. 18       .until( 
  19. 19         () -> assertThat(subscriber.consumedElements) 
  20. 20         .containsExactlyElementsOf(items) 
  21. 21     ); 
  22. 22} 

注意,在publisher实例上调用close()方法。它将在每个订阅者上调用onComplete()。

程序输出如下:

  1. 1Got : 1 
  2. 2Got : x 
  3. 3Got : 2 
  4. 4Got : x 
  5. 5Got : 3 
  6. 6Got : x 
  7. 7Done 

消息的转换

假设还希望在发布者和订阅者之间做一些数据的转换。

下面我创建一个TransformProcessor类,它实现了Processor并扩展了SubmissionPublisher,因为它同时包含Publisher和Subscriber。

并且将传入一个Function将输入转换到输出。

  1.  1import java.util.concurrent.Flow; 
  2.  2import java.util.concurrent.SubmissionPublisher; 
  3.  3import java.util.function.Function
  4.  4 
  5.  5public class TransformProcessor<T,R> extends SubmissionPublisher<R> implements Flow.Processor<T,R> { 
  6.  6    private Function<T,R> function
  7.  7    private Flow.Subscription subscription; 
  8.  8 
  9.  9    public TransformProcessor(Function<T, R> function) { 
  10. 10        super(); 
  11. 11        this.function = function
  12. 12    } 
  13. 13 
  14. 14    @Override 
  15. 15    public void onSubscribe(Flow.Subscription subscription) { 
  16. 16        this.subscription = subscription; 
  17. 17        subscription.request(1); 
  18. 18    } 
  19. 19 
  20. 20    @Override 
  21. 21    public void onNext(T item) { 
  22. 22        submit(function.apply(item)); 
  23. 23        subscription.request(1); 
  24. 24    } 
  25. 25 
  26. 26    @Override 
  27. 27    public void onError(Throwable t) { 
  28. 28        t.printStackTrace(); 
  29. 29    } 
  30. 30 
  31. 31    @Override 
  32. 32    public void onComplete() { 
  33. 33        close(); 
  34. 34    } 
  35. 35} 

这里的TransformProcessor将把String转换为两个String,看下面我写的测试用例。

  1.  1 @Test 
  2.  2    public void whenSubscribeAndTransformElements_thenShouldConsumeAll() { 
  3.  3        // given 
  4.  4        SubmissionPublisher<String> publisher = new SubmissionPublisher<>(); 
  5.  5        Function<String, String> dup = x -> x.concat(x); 
  6.  6        TransformProcessor<String, String> transformProcessor 
  7.  7                = new TransformProcessor<>(dup); 
  8.  8        EndSubscriber<String> subscriber = new EndSubscriber<>(6); 
  9.  9        List<String> items = List.of("1""2""3"); 
  10. 10        List<String> expectedResult = List.of("11""22""33"); 
  11. 11        // when 
  12. 12        publisher.subscribe(transformProcessor); 
  13. 13        transformProcessor.subscribe(subscriber); 
  14. 14        items.forEach(publisher::submit); 
  15. 15        publisher.close(); 
  16. 16 
  17. 17        await().atMost(1000, TimeUnit.MILLISECONDS) 
  18. 18                .untilAsserted(() -> assertTrue(subscriber.consumedElements.containsAll(expectedResult))); 
  19. 19    } 

使用订阅控制消息需求

假设只想消费第一个消息,应用一些逻辑并完成处理。可以使用request()方法来实现这一点。

修改下代码:

  1.  1public class EndSubscriber<T> implements Flow.Subscriber<T> { 
  2.  2    // 多少消息需要消费 
  3.  3    private final AtomicInteger howMuchMessagesToConsume; 
  4.  4    private Flow.Subscription subscription; 
  5.  5    // 保存消费过的消息 
  6.  6    public List<T> consumedElements = new LinkedList<>(); 
  7.  7 
  8.  8    public EndSubscriber(Integer howMuchMessagesToConsume) { 
  9.  9        this.howMuchMessagesToConsume = new AtomicInteger(howMuchMessagesToConsume); 
  10. 10    } 
  11. 11 
  12. 12    @Override 
  13. 13    public void onSubscribe(Flow.Subscription subscription) { 
  14. 14        this.subscription = subscription; 
  15. 15        subscription.request(1); 
  16. 16    } 
  17. 17 
  18. 18    @Override 
  19. 19    public void onNext(T item) { 
  20. 20        howMuchMessagesToConsume.decrementAndGet(); // 减一 
  21. 21        System.out.println("Got : " + item); 
  22. 22        consumedElements.add(item); 
  23. 23        if (howMuchMessagesToConsume.get() > 0) { 
  24. 24            subscription.request(1); 
  25. 25        } 
  26. 26    } 
  27. 27 
  28. 28    @Override 
  29. 29    public void onError(Throwable t) { 
  30. 30        t.printStackTrace(); 
  31. 31    } 
  32. 32 
  33. 33    @Override 
  34. 34    public void onComplete() { 
  35. 35        System.out.println("Done"); 
  36. 36    } 
  37. 37} 

测试

  1.  1@Test 
  2.  2public void whenRequestForOnlyOneElement_thenShouldConsumeOne(){ 
  3.  3    // given 
  4.  4    SubmissionPublisher<String> publisher = new SubmissionPublisher<>(); 
  5.  5    EndSubscriber<String> subscriber = new EndSubscriber<>(1); 
  6.  6    publisher.subscribe(subscriber); 
  7.  7    List<String> items = List.of("1""x""2""x""3""x"); 
  8.  8    List<String> expected = List.of("1"); 
  9.  9 
  10. 10    // when 
  11. 11    assertEquals(publisher.getNumberOfSubscribers(),1); 
  12. 12    items.forEach(publisher::submit); 
  13. 13    publisher.close(); 
  14. 14 
  15. 15    // then 
  16. 16    await().atMost(1000, TimeUnit.MILLISECONDS) 
  17. 17            .untilAsserted(() -> 
  18. 18                    assertTrue(subscriber.consumedElements.containsAll(expected)) 
  19. 19            ); 
  20. 20} 

尽管发布者发布了6个元素,但EndSubscriber将只使用一个元素,因为它表示只需要处理这一个元素。

通过在Subscription上使用request()方法,我们可以实现更复杂的回压机制来控制消息消费的速度。

责任编辑:武晓燕 来源: 码小菜
相关推荐

2023-08-31 16:47:05

反应式编程数据流

2022-08-15 09:00:00

JavaScript前端架构

2022-03-29 07:32:38

R2DBC数据库反应式

2023-12-26 08:15:11

反应式远程接口

2023-09-21 08:01:27

SpringR2DBC实现数据库

2021-03-22 08:45:30

异步编程Java

2021-05-07 16:19:36

异步编程Java线程

2024-01-31 08:26:44

2015-07-30 10:05:37

Java9JShell

2017-12-06 16:28:59

JDK 9JDK 8开发者

2023-04-10 07:44:04

java9java21java

2014-09-12 10:46:35

Java9

2020-02-06 19:12:36

Java函数式编程编程语言

2013-04-01 15:38:54

异步编程异步编程模型

2015-09-16 15:11:58

C#异步编程

2011-07-27 14:10:43

javascript

2023-01-12 11:23:11

Promise异步编程

2015-07-16 09:52:40

Java9新特性软件开发

2021-01-19 05:30:55

C# 8异步流IEnumerable

2021-08-02 11:13:28

人工智能机器学习技术
点赞
收藏

51CTO技术栈公众号