Spring中如何将WebClient返回的Mono列表赋值给DTO对象属性
你定义了包含客户列表属性的PeopleDTO类,代码如下:
@Data @NoArgsConstructor @AllArgsConstructor public class PeopleDTO { private String processType; private String operation; private String entity; private String entityType; private Long id; private Document document; @Getter @Setter class Customer { private String systemId; private String customerId; } private List<Customer> customers; }
你通过WebClient调用其他微服务获取上述客户列表,对应实现方法getCuCoPerson返回值为Mono<CuCoPerson>类型,方法内已完成客户数据的规则过滤,代码如下:
public Mono<CuCoPerson> getCuCoPerson(Integer cucoId, String GS_AUTH_TOKEN) { WebClient webClient = WebClient.create(); return webClient.get() .uri(GET_RELATION_BY_ID + cucoId) .header("Accept", "application/json") .header("Authorization", GS_AUTH_TOKEN) .retrieve() .bodyToMono(CuCoPerson.class) .map(cuCoPerson -> { List<CustomerRelation> matches = cuCoPerson.getRelatedCustomers() .stream() .filter(relation -> relation.getSystemId().equals(400) || relation.getSystemId().equals(300) || relation.getSystemId().equals(410)) .filter(relation -> relation.getCustomerId().contains("F")) .collect(Collectors.toList()); cuCoPerson.setRelatedCustomers(matches); return cuCoPerson; }); }
该方法返回的cucoPerson数据结构示例如下:
{ "id": 1, "relatedCustomers": [ { "customerId": "xxx", "systemId": 999 } ] }
目前你需要将获取到的客户数据封装到PeopleDTO类实例中,已完成基础属性的硬编码赋值,但不清楚如何处理Mono类型数据完成customers属性的赋值,当前未完成的createPeople方法代码如下:
public PeopleDTO createPeople(Long id) { PeopleDTO people = new PeopleDTO(); people.setProcessType("ONLINE"); people.setOperation("UPDATE"); people.setEntity("DOCUMENT"); people.setEntityType("DOCUMENT"); people.setIdCuco(id); people.setDocument(new Document()); people.setCustomers(......); }
最终期望得到的序列化后JSON结构如下:
{ "type": "ONLINE", "operation": "UPDATE", "id": 1, "entity": "DOCUMENT", "entityType": "NIE", "documents": { "id": 1, "additionals": { "issuing_authority": "Spain", "country_doc": "ES", "place_of_birth": "", "valid_from": "1995-08-09", "valid_to": "0001-01-01" }, "code": "X12345", "typeDocument": "NIE" }, "id": 1, "relatedCustomers": [ { "customerId": "xxx", "systemId": 999 } ] }
核心问题:Mono是Project Reactor提供的异步响应式包装类型,代表一个未来会返回0或1个结果的异步任务,不能直接同步取值赋值,需要根据项目运行环境选择对应实现方式。
方案1:非阻塞响应式写法(推荐,适配WebFlux环境)
不要返回同步的PeopleDTO,直接将方法返回值改为Mono<PeopleDTO>,在响应式操作符链路内完成属性转换和赋值,全程不阻塞,适配WebClient的响应式编程模型。
首先需要修正PeopleDTO的内部类定义,给Customer加static修饰,否则会出现序列化、实例化异常:
@Data @NoArgsConstructor @AllArgsConstructor public class PeopleDTO { private String processType; private String operation; private String entity; private String entityType; private Long id; private Document document; @Getter @Setter public static class Customer { // 必须加static修饰内部类 private String systemId; private String customerId; } private List<Customer> customers; }
修正后的createPeople方法实现:
public Mono<PeopleDTO> createPeople(Long id, String GS_AUTH_TOKEN) { return getCuCoPerson(id.intValue(), GS_AUTH_TOKEN) .map(cuCoPerson -> { PeopleDTO people = new PeopleDTO(); // 基础属性赋值,注意和最终JSON的字段值对齐 people.setProcessType("ONLINE"); people.setOperation("UPDATE"); people.setEntity("DOCUMENT"); people.setEntityType("NIE"); people.setId(id); // 替换为你自己的Document构造逻辑 people.setDocument(buildTargetDocument(id)); // 类型转换:将CuCoPerson的CustomerRelation转为PeopleDTO的Customer List<PeopleDTO.Customer> customers = cuCoPerson.getRelatedCustomers() .stream() .map(relation -> { PeopleDTO.Customer customer = new PeopleDTO.Customer(); customer.setCustomerId(relation.getCustomerId()); // 注意类型转换:CuCoPerson中systemId是数值,PeopleDTO中是字符串 customer.setSystemId(String.valueOf(relation.getSystemId())); return customer; }).collect(Collectors.toList()); people.setCustomers(customers); return people; }); }
需要额外注意的点:
- 你定义的DTO字段名和最终期望JSON的字段名存在差异(比如DTO中
processType对应JSON的type、DTO中document对应JSON的documents),如果要完全匹配序列化结果,需要在对应字段上加@JsonProperty注解指定序列化名称 - 如果远程接口返回为空,需要加
onErrorResume、defaultIfEmpty等操作符做异常和空值处理,避免NPE
方案2:同步阻塞取值(仅适用于Servlet环境,禁止在WebFlux EventLoop线程中使用)
如果你的项目是传统Spring MVC Servlet环境,必须返回同步的PeopleDTO对象,可以调用block()方法从Mono中阻塞获取结果,必须设置超时时间避免无限等待:
public PeopleDTO createPeople(Long id, String GS_AUTH_TOKEN) { PeopleDTO people = new PeopleDTO(); people.setProcessType("ONLINE"); people.setOperation("UPDATE"); people.setEntity("DOCUMENT"); people.setEntityType("NIE"); people.setId(id); people.setDocument(new Document()); // 阻塞等待远程接口返回,设置5秒超时 CuCoPerson cuCoPerson = getCuCoPerson(id.intValue(), GS_AUTH_TOKEN) .block(Duration.ofSeconds(5)); // 判空避免空指针 if (cuCoPerson != null) { List<PeopleDTO.Customer> customers = cuCoPerson.getRelatedCustomers() .stream() .map(relation -> { PeopleDTO.Customer customer = new PeopleDTO.Customer(); customer.setCustomerId(relation.getCustomerId()); customer.setSystemId(String.valueOf(relation.getSystemId())); return customer; }).collect(Collectors.toList()); people.setCustomers(customers); } return people; }
注意:如果在WebFlux的Netty工作线程中调用block()方法,会抛出BlockingOperationError异常,绝对不要在响应式链路中使用阻塞写法。
内容的提问来源于stack exchange,提问作者DiegoMG

