未来:spring响应式编程 Hands-On Reactive Programming in Spring 5(三),spring事件监听

Implementing business logic(实现业务逻辑)

Implementing business logic
在这里插入图片描述
事件发送者接收到消息之后 以一个 Controller (mvc 三层架构中的controller 层)发送给 人 ,让人知道现在的温度 。

In this use case, the domain model will consist only of the Temperature class with the only
double value inside. For simplicity purposes, it is also used as an event object, as shown in
the following code:
Temperature类 将有一个 double 属性 ,他仅仅是一个事件对象(观察者模式中的事件对象)

final class Temperature {
private final double value;
// constructor & getter...
}

To simulate the sensor, let’s implement the TemperatureSensor class and decorate it with
a @Component annotation to register the Spring bean, as follows:
为了模仿传感器 ,让我们实现温度传感器类 并且 注册进spring 中成为一个bean对象

@Component
public class TemperatureSensor {

// publisher:事件发布者
private final ApplicationEventPublisher publisher; // (1)

// 随机数 模拟实时变化的温度
private final Random rnd = new Random(); // (2)

// executor :线程池对象 单例线程,任意时间池中只能有一个线程
private final ScheduledExecutorService executor = // (3)
  Executors.newSingleThreadScheduledExecutor();


public TemperatureSensor(ApplicationEventPublisher publisher) {
  this.publisher = publisher;
}

// 线程池
public void startProcessing() { // (4)
  this.executor.schedule(this::probe, 1, SECONDS);
 }
 
// 事件被发布者收到 , 然后 事件发布者 给所有订阅者发送 这个事件
private void probe() { // (5)

double temperature = 16 + rnd.nextGaussian() * 10;

publisher.publishEvent(new Temperature(temperature));
// schedule the next read after some random delay (0-5 seconds)
executor.schedule(this::probe, rnd.nextInt(5000), MILLISECONDS); //
 (5.1)
  }
}

》》关于了解更多线程池的知识 ,请访问《《

So, our simulated temperature sensor only depends on the ApplicationEventPublisher
class (1), provided by Spring Framework. This class makes it possible to publish events to
the system. It is a requirement to have a random generator (2) to contrive temperatures
with some random intervals. An event generation process happens in a separate
Scheduled ExecutorService (3), where each event’s generation schedules the next
round of an event’s generation with a random delay (5.1). All that logic is defined in the
probe() method (5). In turn, the mentioned class has the startProcessing() method
annotated with @PostConstruct (4), which is called by Spring Framework when the
bean is ready and triggers the whole sequence of random temperature values.
我们模拟的 温度计 只依赖于 ApplicationEventPublisher这个类 ,
ApplicationEventPublisher:可以发布事件给订阅者们
光看上面的类就已经知道这个类需要干啥了其实,或许这就是java语言的魅力

Asynchronous HTTP with Spring Web MVC

The introduced in Servlet 3.0 asynchronous support expands the ability to process an HTTP
request in non-container(非容器对象) threads. Such a feature is pretty useful for long-running tasks.
With those changes, in Spring Web MVC we can return not only a value of type T in
@Controller but also a Callable or a DeferredResult. The Callable may
be run inside a non-container thread, but still, it would be a blocking call. In
contrast, DeferredResult allows an asynchronous response generation on a noncontainer
thread by calling the setResult(T result) method so it could be used within
the event-loop.

  1. Servlet 3.0 支持在一个非容器线程中异步处理http请求,这十 适合长时间的线程任务
  2. 在 springmvc中 我们不仅可以在@Controller 中返回一个T类型 ,也可以 Callable(可以有返回值的线程对线)
  3. Callable 是非容器的线程 , 但他是一个阻塞请求 ,(主线程必须主动去获取返回的参数!!)
  4. DeferredResult
  5. setResult 这个方法我没用过~~ 直接水过
  6. 好吧,就是DeferredResult 运行 非容器内的线程调用 setResult方法 产生一个异步的响应结果(说白了就是异步回调 ,用一个线程去主动获取结果…)

Starting from version 4.2, Spring Web MVC makes it possible to return
ResponseBodyEmitter, which behaves similarly to DeferredResult, but can be used to
send multiple objects, where each object is written separately with an instance of a message
converter (defined by the HttpMessageConverter interface).
servlet 4.2 又进行了一次更新 ,DeferredResult 废弃

Exposing the SSE endpoint

The next step requires adding the TemperatureController class with the
@RestController annotation, which means that the component is used for HTTP
communication, as shown in the following code:
1.加了 @RestController 注解的组件可以接收http请求


@RestController
public class TemperatureController {
//CopyOnWriteArraySet:线程安全的set
private final Set<SseEmitter> clients = new CopyOnWriteArraySet<>();

@RequestMapping(
value = "/temperature-stream", // (2)
method = RequestMethod.GET)
public SseEmitter events(HttpServletRequest request) {

// 相当于一次与客户的连接 ,连接后 handleMessage()方法将会将接收的消息传递给 emitter 
SseEmitter emitter = new SseEmitter(); // (4)
clients.add(emitter); // (5)
// Remove emitter from clients on error or disconnect
emitter.onTimeout(() -> clients.remove(emitter)); // (6)
emitter.onCompletion(() -> clients.remove(emitter)); // (7)
return emitter; // (8)
}

// 这是一个事件监听方法,Temperature :这是事件 ,异步监听事件@Async + @EventListener // (10)
@Async // (9)
@EventListener // (10)
public void handleMessage(Temperature temperature) { // (11)
List<SseEmitter> deadEmitters = new ArrayList<>(); // (12)
clients.forEach(emitter -> {
try {

// 事件监听到了之后 发送给事件接收者
emitter.send(temperature, MediaType.APPLICATION_JSON);
   } catch (Exception ignore) {
    deadEmitters.add(emitter); // (14)
  }
});
clients.removeAll(deadEmitters); // (15)
  }
}

关于@ @EventListener

p61 Now, to understand the logic of the TemperatureController class, we need to describe
the SseEmitter. Spring Web MVC provides that class with the sole purpose of sending
SSE events. When a request-handling method returns the SseEmitter instance, the actual
request processing continues until SseEnitter.complete(), an error, or a timeout
occurs.
让我们开始了解 TemperatureController
SseEmitter:用来发送 SSE 事件

The TemperatureController provides one request handler (3) for the URI
/temperature-stream (2) and returns the SseEmitter (8). In the case when a client
requests that URI, we create and return the new SseEmitter instance (4) with its
previous registration in the list of the active clients (5). Furthermore, the SseEmitter
constructor may consume the timeout parameter(超时参数).
/temperature-stream:这个映射用来 返回 SseEmitter
当客户端请求 URI,我们会创建并且放回一个新的 SseEmitter 实例
SseEmitter 可以

For the clients’ collection, we may use the CopyOnWriteArraySet class from
the java.util.concurrent package (1). Such an implementation allows us to modify
the list and iterate over it at the same time. When a web client opens a new SSE session, we
add a new emitter to the clients’ collection.
CopyOnWriteArraySet 用来存储 emitter
当一个新的会话, CopyOnWriteArraySet 我们将新增一个emitter并进行存储在clients集合里

The SseEmitter removes itself
from the clients’ list when it has finished processing or has reached timeout (6) (7).
Now, having a communication channel with clients means that we need to be able to
receive events about temperature changes.
当 clients’中的 SseEmitter 们已经完成了 处理请求 或者 SseEmitter到达超时的时候我们将删除他,
现在我们有一个clients 的 交流的连接管道,这个管道可以接收温度改变的事件

For that purpose, our class has
a handleMessage() method (11). It is decorated with the @EventListener annotation
(10) in order to receive events from Spring. This framework will invoke
the handleMessage() method only when receiving Temperature events, as this type of
method’s argument is known as temperature. The @Async annotation (9) marks a
method as a candidate for the asynchronous execution, so it is invoked in the manually
configured thread pool. The handleMessage() method receives a new temperature event
and asynchronously sends it to all clients in JSON format in parallel for each event (13).
Also, when sending to individual emitters, we track all failing ones (14) and remove them
from the list of the active clients (15). Such an approach makes it possible to spot clients
that are not operational anymore. Unfortunately, SseEmitter does not provide any
callback for handling errors, and can be done by handling errors thrown by the send()
method only.
@EventListener :用来监听(Temperature )事件,
@Async:使该方法异步执行 ,会在手动配置的线程池中调用被注解标记的方法
handleMessage() :用来发送事件给事件接收者们

Configuring asynchronous support ‘

配置异步的支持

To run everything, we need an entry point for our application with the following
customized methods:

@EnableAsync // (1)
@SpringBootApplication // (2)
public class Application implements AsyncConfigurer {
public static void main(String[] args) {

SpringApplication.run(Application.class, args);
}

//异步线程池
@Override
public Executor getAsyncExecutor() { // (3)
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();// (4)
executor.setCorePoolSize(2);
executor.setMaxPoolSize(100);
executor.setQueueCapacity(5); // (5)
executor.initialize();
  return executor;
  }
  
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler(){
return new SimpleAsyncUncaughtExceptionHandler(); // (6)
  }
}

详细 由很多同学 不知道 AsyncUncaughtExceptionHandler 这个类有啥用
翻翻源码:

public class SimpleAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler {
    private static final Log logger = LogFactory.getLog(SimpleAsyncUncaughtExceptionHandler.class);

    public SimpleAsyncUncaughtExceptionHandler() {
    }

    public void handleUncaughtException(Throwable ex, Method method, Object... params) {
        if (logger.isErrorEnabled()) {
            logger.error("Unexpected exception occurred invoking async method: " + method, ex);
        }

    }
}

就是个捕获异步任务执行异常的方法 Unexpected exception occurred invoking async method
p46

As we can see, the example is a Spring Boot application (2), with an asynchronous
execution enabled by the @EnableAsync annotation (1). Here, we may configure an
exception handler for exceptions thrown from the asynchronous execution (6). That is also
where we prepare Executor for asynchronous processing. In our case, we use
ThreadPoolTaskExecutor with two core threads that may be increased to up to one
hundred threads. It is important to note that without a properly configured queue
capacity (5), the thread pool is not able to grow. That is because the SynchronousQueue
would be used instead, limiting concurrency.
SynchronousQueue 将取代 ThreadPoolTaskExecutor

  1. 我们需要配置异常捕获机制
    2.我们需要准备两个核心线程在线程池,这两条线程有可能增加到100
    3.队列不需要配置队列的数量】、、
    4.线程池不能自动增长,限制了并发性
    解释 :监听者捕获事件,开启异步任务消耗事件, 异步任务的开启需要消耗线程池中的线程,再多点任务 ,线程池就无法承受 。所以选择消息队列 。一条消息队列就可以不断的 接收事件

Criticism of the solution

At this point, we may praise ourselves for implementing a resilient (弹性的)reactive application
using only a few dozen code lines (including HTML and JavaScript). However, the current
solution has a few issues. First of all, we are using the Publish-Subscribe infrastructure
provided by Spring. In Spring Framework, this mechanism was initially introduced for
handling application life cycle events, and was not intended for high-load, high performance
scenarios. What would happen when, instead of one stream of temperature
data, we need thousands or even millions of separate streams? Will Spring’s
implementation be able to handle such a load efficiently?
spring框架最初是为了处理 应用程序生命周期事件的,不是为高负载,高性能场景设计的,在spring框架中开
发布订阅 模式 spring会开启百万甚至千万的事件流…

One more drawback worth highlighting is that we allocate the thread pool to
asynchronously broadcast temperature events. In the case of a genuinely asynchronous and
reactive approach (framework), we wouldn’t have to do this.
Our temperature sensor generates only one stream of events without regard to how many
clients are listening. However, it also creates them when nobody listens. That may lead to a
waste of resources, especially when creation actions are resource hungry. For example, our
component may communicate with real hardware and reduce hardware lifespan at the
same time
我们的温度传感器只产生一个事件流,不管有多少客户监听 。
然而线程池中的线程(用来接收事件的线程)无时无刻都在 ,这样浪费了资源,导致其他线程饥饿

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值