看完这篇文章你会有很大收获!
好学近乎知,力行近乎仁,知耻而后勇.
The previous chapter explained why it is important to build reactive systems and how
reactive programming helps to do this. In this section, we will look at some toolsets that have
already been present in Spring Framework for some time. We will also learn the important basic
concepts of reactive programming by exploring the RxJava library, which is the first and most
well-known reactive library in the Java world
这一章节我们要来谈谈.spring响应式编程了
We have previously mentioned that there are a lot of patterns and programming techniques
that are capable of becoming building blocks for the reactive system. For example, callbacks
and CompletableFuture are commonly used to implement the message-driven
architecture. We also mentioned reactive programming as a prominent candidate for such a
role. Before we explore this in more detail, we need to look around and find other solutions
that we have already been using for years.
1.早期使用CompletableFuture (java多线程种的开辟异步线程的一个类)和 callbacks(异步回调)来解决阻塞问题
2.异步编程也是一个很好的备选方案
3.我们也需要找到其他的解决方案
newer Java 8 CompletableFuture,
which introduces some neat methods for asynchronous execution composition.
Nevertheless, Spring Framework provides other bits of infrastructure that will be very
useful for building our reactive application. Let’s look through some of these features now.
早期的java8提供的CompletableFuture是一种解异步编程的解决方案,然而,spring框架有自己的提供的解决响应式编程的方案
To move things along(随着事情的发展), we need to remind(回忆起) ourselves about a particular pretty old and wellknown
design pattern—the Observer pattern. That is one of the twenty-three famous GoF
(Gang of Four) design patterns. At first glance, it may appear that the Observer pattern is
not related to reactive programming. However, as we will see later, with some small
modifications, it defines the foundations of reactive programming.
To read more about GoF design patterns, please refer to Design
Patterns: Elements of Reusable Object-Oriented Software by Erich Gamma,
Richard Helm, Ralph Johnson, and John Vlissides (https:/ / en.
wikipedia. org/ wiki/ Design_ Patterns).
The Observer pattern involves a subject that holds a list of its dependants, called
Observers. The subject notifies its observers of any state changes, usually by calling one of
their methods. This pattern is essential when implementing (贯彻)systems based on event
handling. The Observer pattern is a vital part of the MVC (Model-View-Controller)
pattern. Consequently, almost all UI libraries apply it internally.
1.提出 观察者模式 :一种古老且优雅的模式,23种设计模式之一,
被观察者 做出状态变更 通过 调用观察者的方法 提醒观察者自己的变更
这种模式使用在 事件驱动的 系统中,在MVC 架构种是一个非常重要的组件,几乎所有的UI库都在内部应用它。
To simplify this, let’s use an analogy (类比)from an everyday situation. We can apply this pattern
to the newsletter subscription from one of the technical portals(门户网站). We have to register our
email address somewhere on the site of our interest, and then it will send us notifications in
the form of newsletters, as shown in the following diagram:
为了解释上面的这种情况,我举个例子,我们能够订阅门户网站获得实时通讯,我们必须注册我们的
电子邮件地址在我们感兴趣的网站上,然后该网站会以实时通信的形式 会发送给我们一个通知 。如下图
The Observer pattern makes it possible to register one-to-many dependencies between
objects at runtime. At the same time, it does this without knowing anything about the
component implementation details (to be type safe, an observer may be aware of a type of
incoming event). That gives us the ability to decouple application pieces even though these
parts actively interact. Such communication is usually one directional and helps efficiently
distribute events through the system, as shown in the following diagram:
观察者能够依赖一到多个对象,与此同时,观察者不需要了解组件的任何细节,观察者也许只需要知道 传入事件。下图这种设计具有很强的解耦合能力,这种 交流是单项的,通过这种系统具有很强的分发事件。
1.只能被观察者通知观察者 (单项交流)
2.一个观察者接收多个被观察者的发过来的事件 (强的分发能力)
As the preceding diagram shows, a typical Observer pattern consists of two
interfaces, Subject(被观察对象) and Observer. Here, an Observer is registered in Subject and
listens for notifications from it. A Subject may generate events on its own or may be called
by other components. Let’s define a Subject interface in Javazh
上面有两个接口,一个是观察者,一个是被观察者, 观察者被注册进观察者对象,
所谓的监听就是被观察对象提醒 观察者~~
public interface Subject<T> {
void registerObserver(Observer<T> observer);
void unregisterObserver(Observer<T> observer);
void notifyObservers(T event);
}
This generic interface is parametrized with the event type T, which improves the type
safety of our programme. It also contains methods for managing subscriptions
(the registerObserver, unregisterObserver, and notifyObservers methods) that
trigger an event’s broadcasting. In turn, the Observer interface may look like the
following:
通用接口将 事件类进行泛化,那可以保护程序的类型安全.有三个管理订阅者的方法,分别是增删订阅者,和提醒订阅者.
被观察者会触发事件的广播,相反而言,观察者接口
public interface Subject {
void registerObserver(Observer observer);
void unregisterObserver(Observer observer);
void notifyObservers(T event);
}
public interface Observer<T> {
void observe(T event);
}
The Observer is a generic interface, which parametrized with the T type . In turn, it has
only one observe method in place to handle events. Both the Observer and Subject do
not know about each other more than described in these interfaces.
The Observer implementation may be responsible for the subscription procedure, or
the Observer instance may not be aware of the existence of the Subject at all. In the latter
case, a third component may be responsible for finding all of the instances of
the Subject and all registration procedures. For example, such a role may come into
play with the Dependency Injection container. This scans the classpath for each
Observer with the @EventListener annotation and the correct signature. After that, it
registers the found components to the Subject.
观察者是一个通用的接口,观察者具有一个泛型参数. 相反而言,他仅仅拥有一个 observe 方法这个方法可以处理事件,
观察者和被观察者对彼此并不了解在这些接口之中
观察者 对 订阅程序 进行响应 或者 观察者根本不关系订阅者的全部。
同时一个三方组件越需要找到所有的被观察者,和所有的注册手续(被观察者需要注册进观察者),
例如,有一个角色 ,他将在容器的依赖注入中发挥作用,使用 @EventListener 注解 扫描观察者的类路径和签名,过后 观察者将被注册成一个组件 ,这个组件 观察者可以发现。
下面是一个简单的案例
观察者 A
public class ConcreteObserverA implements Observer<String> {
@Override
public void observe(String event) {
System.out.println("Observer A: " + event);
观察者 B
public class ConcreteObserverB implements Observer<String> {
@Override
public void observe(String event) {
System.out.println("Observer B: " + event);
}
}
We also need to write an implementation of the Subject, which
produces String events, as shown in the following code:
具体的被观察者对象(也是事件发布的 对象)
public class ConcreteSubject implements Subject<String> {
// 这个set 里面包含了所有的 被观察者
private final Set<Observer<String>> observers = new CopyOnWriteArraySet<>();
// 这个方法 用来注册观察者
public void registerObserver(Observer<String> observer) {
observers.add(observer);
}
// 这个方法 删除保存的观察者
public void unregisterObserver(Observer<String> observer) {
observers.remove(observer);
}
// 提醒观察者 ,遍历所有观察者 让观察者接收 自己发出的事件
public void notifyObservers(String event) {
observers.forEach(observer -> observer.observe(event));
}
}
As we can see from the preceding example, the implementation of the Subject holds the
Set of observers (1) that are interested in receiving notifications. In turn, a modification
(subscription or cancellation of the subscription) of the mentioned Set is
possible with the support of the registerObserver and unregisterObserver methods.
To broadcast events, the Subject has a notifyObservers method (2) that iterates over
the list of observers and invokes the observe() method with the actual event (2.1) for
each Observer. To be secure in the multithreaded scenario, we
use CopyOnWriteArraySet, a thread-safe Set implementation that creates a new copy of
its elements each time the update operation happens. It is relatively expensive to update
the contents of the CopyOnWriteArraySet, especially when the container holds a lot of
elements. However, the list of subscribers does not usually change often, so it is a fairly
reasonable option for the thread-safe Subject implementation.
根据上图我们可以发现 ,被观察者 拥有设置 对接收通知感冒的观察者 的一个方法 ,
相反一个被提到的方法 set(订阅和 取消订阅 )用来 注册和 删除订阅者。
为了广播 ,被观察者有一个提醒全部观察者的方法(采用遍历的方式) ,并且需要调用观察者的 一个接收方法,
为了确保在多线程情况下的安全 ,
我们使用 CopyOnWriteArraySet 这个 set集合: 他更新的时候创建他里面元素新的副本,然而,相对而言,更新
CopyOnWriteArraySet 里面元素的代价是昂贵的,尤其里面有很多的元素的时候。但是 订阅者 通常情况下是不怎么会进行变更的 ,
所以他是一个线程非常安全的set集合 也非常适合用来保证 观察者模式的线程安全
@Test
public void observersHandleEventsFromSubject() {
// given
Subject<String> subject = new ConcreteSubject();
Observer<String> observerA = Mockito.spy(new ConcreteObserverA());
Observer<String> observerB = Mockito.spy(new ConcreteObserverB());
// when
subject.notifyObservers("No listeners");
subject.registerObserver(observerA);
subject.notifyObservers("Message for A");
subject.registerObserver(observerB);
subject.notifyObservers("Message for A & B");
subject.unregisterObserver(observerA);
subject.notifyObservers("Message for B");
subject.unregisterObserver(observerB);
subject.notifyObservers("No listeners");
// then
Mockito.verify(observerA, times(1)).observe("Message for A");
Mockito.verify(observerA, times(1)).observe("Message for A & B");
Mockito.verifyNoMoreInteractions(observerA);
Mockito.verify(observerB, times(1)).observe("Message for A & B");
Mockito.verify(observerB, times(1)).observe("Message for B");
Mockito.verifyNoMoreInteractions(observerB);
}
@Test
public void subjectLeveragesLambdas() {
Subject<String> subject = new ConcreteSubject();
subject.registerObserver(e -> System.out.println("A: " + e));
subject.registerObserver(e -> System.out.println("B: " + e));
subject.notifyObservers("This message will receive A & B");
...
}
private final ExecutorService executorService =
Executors.newCachedThreadPool();
public void notifyObservers(String event) {
observers.forEach(observer ->
executorService.submit(
() -> observer.observe(event)
)
);
}
To play with the Publish-Subscribe pattern in Spring Framework, let’s do an exercise. In
turn, assume that we have to implement a simple web service that shows the current
temperature in the room. For this purpose, we have a temperature sensor(温度计), which sends
events with the current temperature in Celsius from time to time. We potentially want to
have both mobile and web applications, but for the sake of (为了)conciseness, we are
implementing only a simple web application. Furthermore, as questions of communication
with microcontrollers are out of the scope of this book, we are simulating a temperature
sensor using a random number generator
我们用spring框架来实现这个 模式,我们有一个 service 来展示当前房间温度
我们模拟一个情况,房间里有个温度传感器(温度是随机数模拟的),无时无刻记录者温度的变化。
To make our application following the reactive design, we cannot use an old pulling model
for data retrieval(数据检索). Fortunately, nowadays we have some well-adopted protocols(方案) for
asynchronous message propagation (异步消息椽笔)from a server to a client,
namely WebSockets and Server-Sent Events (SSE). In the current example, we will use the
last one. The SSE allows a client to receive automatic updates from a server, and is
commonly used to send message updates or continuous data streams to a browser. With
the inception(起初) of HTML5, all modern browsers have a JavaScript API called EventSource,(事件源)
which is used by clients who request a particular URL to receive an event stream.
The EventSource also autore connects by default in the case of communication issues. It is
important to highlight that SSE is an excellent candidate for fulfilling communication needs
between components in the reactive system. On par with WebSocket, SSE is used a lot in
this book.
1.我们使用新的框架完成异步通信,例如Websocket 或者是 SSE
SSE 运行客户端自动接收 来自服务端的更新 ,
SSE也通常用来发送更新消息或者持续的数据流到服务器上.
2.在HTML5的起初,所有的 浏览器 都有一个 JavaScript API 叫EventSource
这个api 在客户端经常使用
客户端请求一个特定的URL 去接收事件流
在通信的问题下,EventSource 默认自连接
很重要的一点是,SSE是满足组件间通信需求的优秀框架 在 响应式系统中。
下面是我和 hxd对EventSource的讨论
To implement our usecase, we are using the well-known Spring modules Spring Web and
Spring Web MVC. Our application will not use the new features of Spring 5, so it will run
similarly on Spring Framework 4.x. To simplify our development process and even more,
we are leveraging Spring Boot, which is described in more detail later. To bootstrap our
application, we may configure and download a Gradle project from the Spring Initializer
website at start.spring.io. For now, we need to select the preferred Spring Boot version
and dependency for the web (the actual dependency identifier in Gradle config will
be org.springframework.boot:spring-boot-starter-web), as shown in the
following screenshot:
1.为了完成这个案例 我们使用spring5框架为了简化我们的开发进度,我们使用springboot
我们将配置和下载一个 Gradle 项目
后面的下个章节再更新吧…