我们遇到了以下我们理解的行为,但是我们想知道这是否是预期的,以及将其记录为某种陷阱可能是有意义的。
我们正在尝试使用Spring Boot 2 / Spring WebFlux并设置一个基本上具有类似功能的小应用程序(全部缩短):
h
服务首先看起来像这样,因为我们想要将一个人的事件也发布到消息队列中:
@PostMapping
public Mono<Todo> addTodos( @RequestBody Person person ) {
return personService.addPerson( person );
}
所以,这样做显然是错误的。 public class PersonService {
public Mono<Person> addPerson( Person person ) {
Mono<Person> addedPerson = personRepository.save( person );
addedPerson.subscribe( p -> rabbitTemplate.convertAndSend( "persons", p ) );
return addedPerson;
}
}
触发流程,我们假设响应式REST控制器在序列化响应数据之前在后台执行相同操作,从而产生第二个并行流。最后,我们最终在数据库的.subscribe()
集合中有两个重复的条目。
在这个冗长的介绍之后,最后的问题是:这是多个订阅者触发多个插入的预期行为(基本上,如果您订阅persons
次,则会获得n
次插入)?
如果是,这对初学者来说可能是一个陷阱,特别是如果我们的理解是正确的,那么反应式REST控制器会在引擎盖下执行n
。
答案 0 :(得分:5)
你得出的结论描述了预期的行为。
反应式编程模型与各种领域的命令式编程模型不同。
命令式编程结合了转换,映射,执行和其他方面。您可以通过创建条件/循环流,方法调用来表达这些,这些方法调用可以返回值并将值传递给API调用。
反应式编程将 正在发生什么的声明与 将要执行的内容分离。使用反应式基础设施的执行分为两部分:反应序列组成和实际执行。在您的代码中,您只编写反应序列。执行发生在您的代码之外。
撰写Publisher
时,生成的Publisher
包含如果执行将会发生的事情的声明。 Publisher
并不意味着它是否会首先执行,也不会最终订阅多少订阅者。
从上面的示例中,Mono<Person> PersonRepository.save(…)
返回的发布商:
Person
映射到Document
Document
保存到MongoDB和Person
这是使用特定存储库方法保存数据的方法。创建发布者不会执行发布者,并且发布者不会就执行次数发表意见。多次调用.subscribe()
多次执行发布商。
我认为.subscribe()
不是一个陷阱。反应式编程模型方法会使执行失败。如果您致电.subscribe()
或.block()
,那么您应该有充分的理由这样做。每当您在代码中看到.subscribe()
或.block()
时,您应该特别注意这是否正确。您的执行环境负责订阅Publisher
。
一些观察结果:
RabbitTemplate
是一个阻止API。您不应混合使用反应和阻止API。如果您没有其他选项,则卸载阻止对工作人员的呼叫。在包含阻止作品的实际操作符之前的publishOn(…)
上使用Scheduler
,或者使用ExecutorService
/ CompletableFuture
和flatMap(…)
。flatMap(…)
运算符用于Mono
/ Flux
的反应流组合。 flatMap(…)
运算符启动最终完成的非阻塞子进程并继续流程。doOnXXX(…)
运算符(doOnNext(…)
,doOnSuccess(…)
,...)进行回调。这些钩子方法可以方便地拦截元素非阻塞消耗。参考文献: