响应式流(http://www.reactive-streams.org/)提供了具有非阻塞压力的异步流处理机制。
响应式流基于以下三个要素:
- 信息发布者
- 多个信息订阅者
- 发布者与消费者之间的订阅
Java 9提供三个接口,Flow.Publisher、Flow.Subscriber和Flow.Subscription,以及工具类SubmissionPublisher,用来实现响应式流应用。
本节将学习如何只使用这三个接口实现自定义的响应式流应用。考虑到实现三个元素之间的预期行为,发布者将只向提出请求的订阅者发送元素,并且以并发形式进行此操作。但是通过修改这些方法的实现,可以很容易地修改这种行为。
准备工作
本范例通过Eclipse开发工具实现。如果使用诸如NetBeans的开发工具,打开并创建一个新的Java项目。
实现过程
通过如下步骤实现范例:
-
创建名为News的类,此类实现实现从发布者发送到订阅者的元素,包含两个名为title和content的私有String属性,名为date的Date属性,还包括获取和设置这些属性值的方法。此类代码很简单,不在这里列出。
-
创建名为Consumer的类,指定其实现News类参数化的Subscriber接口,包含两个私有属性:名为subscription的Subscription对象和名为name的String属性。实现类构造函数,初始化name属性:
public class Consumer implements Subscriber<News>{ private Subscription subscription; private String name; public Consumer(String name) { this.name=name; }
-
实现onComplete()方法,当发布者不发送任何附加元素时,调用此方法。本范例中,只输出一条信息到控制台:
@Override public void onComplete() { System.out.printf("%s - %s: Consumer - Completed\n", name, Thread.currentThread().getName()); }
-
实现onError()方法,当有错误发生时,发布者调用此方法。本范例中,只输出一条信息到控制台:
@Override public void onError(Throwable exception) { System.out.printf("%s - %s: Consumer - Error: %s\n", name, Thread.currentThread().getName(), exception.getMessage()); }
-
然后实现onNext()方法。此方法将News对象作为参数接收,当发布者向订阅者发送条目时,调用此对象。本范例中,输出News对象的属性值到控制台,并且使用Subscription对象的request()方法请求附加条目:
@Override public void onNext(News item) { System.out.printf("%s - %s: Consumer - News\n", name, Thread.currentThread().getName()); System.out.printf("%s - %s: Title: %s\n", name, Thread.currentThread().getName(), item.getTitle()); System.out.printf("%s - %s: Content: %s\n", name, Thread.currentThread().getName(), item.getContent()); System.out.printf("%s - %s: Date: %s\n", name, Thread.currentThread().getName(), item.getDate()); subscription.request(1); }
-
最后,实现onSubscription()方法,发布者将调用此方法,是唤醒Subscriber的首个方法。它在发布者和订阅者之间接收Subscription。本范例中, 存储Subscription对象,通过订阅者使用request()方法请求待处理的首个条目:
@Override public void onSubscribe(Subscription subscription) { this.subscription = subscription; subscription.request(1); System.out.printf("%s: Consumer - Subscription\n", Thread.currentThread().getName()); } }
-
实现名为MySubscription的类,指定其实现Subscription接口。包含名为canceled的私有Boolean属性和名为requested的私有整型属性:
public class MySubscription implements Subscription{ private boolean canceled=false; private long requested=0;
-
实现Subscription接口提供的cancel()方法,取消发布者与订阅者之间的通信。本范例中设置canceled属性值为true:
@Override public void cancel() { canceled=true; }
-
实现Subscription接口提供的request()方法,订阅者使用此方法向发布者请求元素。它将订阅者请求的元素数量作为参数接收,本范例中,递增requested属性值:
@Override public void request(long value) { requested+=value; }
-
实现isCanceled()方法获得canceled属性值,getRequested()方法获得requested属性值,以及decreaseRequested()方法递减requested属性值:
public boolean isCanceled() { return canceled; } public long getRequested() { return requested; } public void decreaseRequested() { requested--; } }
-
实现名为ConsumerData的类,发布者使用此方法存储每个订阅者的信息。包含名为consumer的私有Consumer属性和名为subscription的私有MySubscription属性,还包括获取和设置这些属性值的方法。此类代码很简单,不在这里列出。
-
实现名为PublisherTask的类,指定其实现Runnable接口。包含名为consumerData的私有ConsumerData属性和名为news的私有News属性。实现类构造函数,初始化这两个属性:
public class PublisherTask implements Runnable{ private ConsumerData consumerData; private News news; public PublisherTask(ConsumerData consumerData, News news) { this.consumerData = consumerData; this.news = news; }
-
实现run()方法,将得到ConsumerData属性的MySubscription对象。如果subscription未被取消且已经请求元素(属性值大于0),则使用onNext()发送News对象给订阅者,然后减少requested属性值:
@Override public void run() { MySubscription subscription = consumerData.getSubscription(); if (!(subscription.isCanceled() && (subscription.getRequested()> 0))) { consumerData.getConsumer().onNext(news); subscription.decreaseRequested(); } } }
-
然后实现名为MyPublisher的类,指定其实现News类参数化的Publisher接口。此类存储ConsumerData对象的私有ConcurrentLinkedDeque和名为executor的ThreadPoolExecutor对象。实现类构造函数,初始化这两个属性:
public class MyPublisher implements Publisher<News> { private ConcurrentLinkedDeque<ConsumerData> consumers; private ThreadPoolExecutor executor; public MyPublisher() { consumers=new ConcurrentLinkedDeque<>(); executor = (ThreadPoolExecutor)Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); }
-
现在,实现subscribe()方法,此方法将接收以参数的形式接收发布者条目的Subscriber对象。接着创建MySubscription和ConsumerData对象,在ConcurrentLinkedDeque中存储ConsumerData,并且调用订阅者的onSubscribe()发送subscription对象给Subscriber对象:
@Override public void subscribe(Subscriber<? super News> subscriber) { ConsumerData consumerData=new ConsumerData(); consumerData.setConsumer((Consumer)subscriber); MySubscription subscription=new MySubscription(); consumerData.setSubscription(subscription); subscriber.onSubscribe(subscription); consumers.add(consumerData); }
-
实现publish()方法,此方法接收News参数并将其发送给符合之前解释条件的订阅者。 为了实现此操作,为每个订阅者创建PublisherTask方法,并将这些任务发送给执行器:
public void publish(News news) { consumers.forEach( consumerData -> { try { executor.execute(new PublisherTask(consumerData, news)); } catch (Exception e) { consumerData.getConsumer().onError(e); } }); } }
-
最后,添加main()方法,实现本范例主类。创建1个发布者和2个订阅者,并将它们订阅给发布者:
public class Main { public static void main(String[] args) { MyPublisher publisher=new MyPublisher(); Subscriber<News> consumer1, consumer2; consumer1=new Consumer("Consumer 1"); consumer2=new Consumer("Consumer 2"); publisher.subscribe(consumer1); publisher.subscribe(consumer2);
-
然后创建News对象,发送给发布者,休眠主线程1秒钟,创建另一个News对象,在此发送给发布者:
System.out.printf("Main: Start\n"); News news=new News(); news.setTitle("My first news"); news.setContent("This is the content"); news.setDate(new Date()); publisher.publish(news); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } news=new News(); news.setTitle("My second news"); news.setContent("This is the content of the second news"); news.setDate(new Date()); publisher.publish(news); System.out.printf("Main: End\n"); } }
工作原理
本范例中,使用Java 9 API提供的接口实现了发布者和订阅者之间的响应式流通信,且仅遵循响应式流规范中定义的预期行为。
包括MyPublisher类实现的发布者和Consumer类实现的订阅者,以及在发布者和MySubscription对象实现的每个订阅者之间有订阅信息。
当订阅者调用发布者的subscribe()方法时,通信循环开始。发布者在其间创建订阅信息,并使用onSubscribe()方法发送订阅信息给订阅者。订阅者必须使用发布信息的request()方法表明已经准备好处理来自发布者的更多元素。当发布者发布条目时,将此条目发送给所有使用订阅信息从发布者请求元素的订阅者。
我们添加了所有必要的元素确保以并发的方式执行此行为。
下图显示本范例在控制台输出的执行信息:
扩展学习
创建实现响应式流的应用最简单的方式是使用SubsmissionPublisher类,此类实现Publisher接口,并提供必要的方法将其作为应用的发布者部分。
更多关注
- 第六章“并行和响应式流”中的“响应式流编程”小节