Kotlin反应式编程问题:移除.block()改用流式处理后,组合DTO无法正确序列化返回
问题分析与解决方案
我来帮你排查这个问题——你遇到的核心问题是Reactive类型(Mono)的序列化异常:当你把Mono<MutableList<CustomObjectDto>>直接放进响应DTO时,Spring并没有等待异步操作完成、获取实际数据后再序列化,而是直接序列化了Mono对象本身的内部状态,这就导致返回了scanAvailable: true这种非预期结果。
问题根源拆解
- 响应DTO的字段类型错误:你的
CustomObjectResultDto中results字段定义为Mono<MutableList<CustomObjectDto>>,但Spring默认不会自动订阅Reactive类型并提取内部数据,只会序列化对象的元数据(比如scanAvailable)。 - Service层的返回值设计不符合Reactive范式:当前Service直接返回
CustomObjectResultDto,但里面的count和result都是未完成的异步操作(Mono),没有合并异步结果就直接封装成DTO返回了。 - Controller层没有正确处理Reactive响应:Controller返回的是包含未处理
Mono的HttpResponse,框架无法识别需要等待异步操作完成。
修复步骤与代码调整
1. 修正响应DTO的字段类型
去掉results字段的Mono包裹,直接用实际数据类型:
data class CustomObjectResultDto( val resultsLength: Long?, @JsonInclude val results: MutableList<CustomObjectDto> )
2. 重构Service层,返回合并后的异步结果
使用Mono.zip合并count和查询结果两个异步操作,确保两者都完成后再构建最终DTO:
override fun listing( ctx: context, filterConfig: FilterConfig, filterParams: FilterParams ): Mono<CustomObjectResultDto> { // 获取查询结果的Mono val resultMono = repository.listing(ctx, filterParams, filterConfig) .flatMapIterable { list: List<CustomObject> -> list.map { it.toItemDto() } } .collectList() // 获取计数的Mono(确保repository.count返回的是Mono<Long>,如果是阻塞方法请改为Reactive版本) val countMono = repository.count(ctx, filterParams) // 合并两个异步操作,构建最终DTO return Mono.zip(countMono, resultMono) .map { (count, results) -> CustomObjectResultDto(count, results) } }
3. 调整Controller层,返回Reactive响应
让Controller返回Mono<MutableHttpResponse<CustomObjectResultDto>>,或者直接返回Mono<CustomObjectResultDto>(Spring WebFlux会自动处理订阅和序列化):
// 示例:返回Mono包裹的HttpResponse fun listing( // 你的请求参数:partnerId, userId, correlationId等 ): Mono<MutableHttpResponse<CustomObjectResultDto>> { val ctx = context(partnerId, userId, correlationId) // ...日志逻辑... val filterParams = FilterParams(userName, applicationType, status, wifiStatus, cardNumber) val filterConfig = FilterConfig(pageLimit, pageIndex, PAGE_ORDER_BY_VALUES.getValue(orderBy), order) // 订阅Service返回的Mono,构建响应 return service.listing(ctx, filterConfig, filterParams) .map { resultDto -> HttpResponse.ok(resultDto) } }
如果使用注解式Controller(比如@GetMapping),可以更简洁:
@GetMapping("/custom-objects") fun listing( // 请求参数 ): Mono<CustomObjectResultDto> { // ...上下文和参数构建逻辑... return service.listing(ctx, filterConfig, filterParams) }
为什么这样改能解决问题?
- 响应DTO不再包含Reactive类型,序列化时直接处理实际的实体列表,符合你预期的JSON格式。
Mono.zip确保了count和查询结果两个异步操作都完成后才构建DTO,避免了异步操作未完成就返回的问题。- Controller返回
Mono类型,Spring WebFlux会自动订阅这个异步流,等待数据就绪后再序列化返回给客户端。
内容的提问来源于stack exchange,提问作者Kristóf Nagy
相关产品推荐
相关产品推荐

