300字范文,内容丰富有趣,生活中的好帮手!
300字范文 > FeignClient调用 Cannot deserialize instance of `java.lang.Boolean` out of START_OBJECT token to

FeignClient调用 Cannot deserialize instance of `java.lang.Boolean` out of START_OBJECT token to

时间:2024-03-03 01:47:02

相关推荐

FeignClient调用 Cannot deserialize instance of `java.lang.Boolean` out of START_OBJECT token to

异常描述

Caused by: com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.lang.Boolean` out of START_OBJECT token at [Source: (ByteArrayInputStream); line: 1, column: 1]

异常原因

项目分为原子层(直连数据库)和聚合层(调用原子层) (DDD模式),原子上原子层不能互相调用,必须通过聚合层调。聚合层之间可以相互调用(抽取公共jar包)原子层方法定义

# order项目public ResponseEntity<Boolean> saveTmsBookingOrder(@Valid SaveDTO vo){...String siOrders = "测试si单号";if(StringUtils.isNotBlank(siOrders)){throw new ServiceException("si单号:" + siOrders + "已经订舱");}...}

聚合层接口定义

public ResponseEntity<Boolean> saveTmsBookingOrder(@Valid SaveDTO vo){return ***Feign.saveTmsBookingOrder(vo);}

当原子层抛出 ServiceException 异常时(其他异常类似)通过postman直接调用原子层返回结果为

{"failed": true,"code": "500","message": "si单号:WM000004504已经订舱","type": "warn","data": null}

聚合层正常返回数据(json串) 泛型封装在 body 属性中

{"body": true,"headers": {"cache-control": ["no-cache, no-store, max-age=0, must-revalidate"],"connection": ["keep-alive"],"content-type": ["application/json"],"date": ["Wed, 08 Jun 06:42:36 GMT"],"expires": ["0"],"pragma": ["no-cache"],"transfer-encoding": ["chunked"],"x-content-type-options": ["nosniff"],"x-xss-protection": ["1; mode=block"]},"statusCode": "OK","statusCodeValue": 200}

但在聚合层通过Feign调用原子层时,则返回开始时的异常分析异常时,大概意思是返回值不能转换为 java.lang.Boolean

解法1(不建议)

原子层返回为

public ResponseEntity<Boolean> saveTmsBookingOrder(@Valid SaveDTO vo){...String siOrders = "测试si单号";if(StringUtils.isNotBlank(siOrders)){// 正常情况不展示MultiValueMap<String, String> headers = new HttpHeaders();headers.set("errorMessage", URLEncoder.encode("si单号:****已经订舱", StandardCharsets.UTF_8.name()));ResponseEntity<Boolean> response = new ResponseEntity(false, headers, HttpStatus.OK);return response;}...}

将错误信息封装在Header中,当聚合层接收泛型返回值为false时,则从header头取错误描述(errorMessage)统一返回参数封装

import lombok.Getter;import lombok.NoArgsConstructor;import lombok.Setter;import lombok.ToString;import java.io.Serializable;/*** Result* 统一返回参数封装** @author weigang* @create -06-09**/@Getter@Setter@ToString@NoArgsConstructorpublic class Result<T> implements Serializable {private static final long serialVersionUID = 1848261014773540607L;/*** 是否成功*/private boolean success;/*** 错误码*/private String code;/*** 提示语*/private String message;/*** 最好的方式是用泛型,明确返回类型*/private T data;public static <T> Result<T> success(T data) {Result<T> result = new Result<>();result.setSuccess(true);result.setCode("0");result.setData(data);result.setMessage("操作成功");return result;}public static <T> Result<T> error(String code, String message) {Result<T> result = new Result<>();result.setSuccess(false);result.setCode(code);result.setMessage(message);return result;}}

聚合层定义修改为

public ResponseEntity<Result<Boolean>> saveTmsBookingOrder(@Valid SaveDTO vo){ResponseEntity<Boolean> response = ***Feign.saveTmsBookingOrder(vo);if (response.getBody()) {return Results.success(Result.success(true));}String errorMsg = "";List<String> errorList = response.getHeaders().get(CommonConstant.ERROR_MSG);if (CollectionUtils.isNotEmpty(errorList)) {try {errorMsg = URLDecoder.decode(errorList.get(0), StandardCharsets.UTF_8.name());} catch (UnsupportedEncodingException e) {log.error("saveTmsBookingOrder fail, param-> {}, UnsupportedEncodingException-> {}", JSON.toJSONString(vo), e);}}return Results.success(Result.error("-1", errorMsg));}

Results 底层实现

private static final ResponseEntity NO_CONTENT = new ResponseEntity<>(HttpStatus.NO_CONTENT);/*** 请求成功** @param data 返回值* @return HttpStatus 200*/public static <T> ResponseEntity<T> success(T data) {if (data == null) {return NO_CONTENT;}return ResponseEntity.ok(data);}

因为项目中原子层还有很多地方都是返回 ResponseEntity<Boolean>,其他接口也需要改造时,成本太高

解法2(不建议)

原子层和聚合层接口定义都修改为 ResponseEntity<Result<Boolean>>,原子层封装好错误信后返回每个接口都需要修改原子层和聚合层,上线稍不注意,就可能补录数据(返回参数变了)针对少量使用 ResponseEntity<Result<Boolean>> 接口,可以使用这种方式(且流量不大)

解法3

原子层接口定义不变

public ResponseEntity<Boolean> saveTmsBookingOrder(@Valid SaveDTO vo){...}

原子层增加Controller层拦截器

@Pointcut("execution(* com.tcl.***.controller.v1.*.*.*(..)) " +"|| execution(* com.tcl.***.controller.v1.*.*(..))")private void methodAspect() {}@Around("methodAspect()")public Object validate(ProceedingJoinPoint joinPoint) throws Throwable {Signature signature = joinPoint.getSignature();if (!(signature instanceof MethodSignature)) {return null;}MethodSignature methodSignature = (MethodSignature) signature;Method method = methodSignature.getMethod();String className = method.getDeclaringClass().getSimpleName();String methodName = method.getName();Object[] argArr = joinPoint.getArgs();Object argObj = null;if (!Objects.isNull(argArr) && argArr.length > 0) {Object[] arguments = new Object[argArr.length];for (int i = 0; i < argArr.length; i++) {if (argArr[i] instanceof MultipartFile) {continue;}arguments[i] = argArr[i];}argObj = arguments[0];}String traceId = null;try {// REQUEST_ID 通过 logback-spring.xml 可以获取String traceIdParent = MDC.get(CommonConstant.REQUEST_ID);if (StringUtils.isBlank(traceIdParent)) {traceId = UUID.randomUUID().toString().replaceAll("-", "");MDC.put(CommonConstant.REQUEST_ID, traceId);} else {traceId = traceIdParent;}// multipartFile使用json.toJsonString会失败,驳回异常,不打印文件信息String argStr = JSON.toJSONString(argObj);log.info("traceId-> {}, {} {} param-> {}", traceId, className, methodName, argStr);} catch (Exception e) {}Object resultObj;Type typeIndex0 = this.getTypeIndex0(method);try {// 链路ID 通过 header 传到原子层(FeignInterceptor.java)resultObj = joinPoint.proceed();log.info("traceId-> {}, {} {} response-> {}", traceId, className, methodName, JSON.toJSONString(resultObj));return resultObj;} catch (IllegalArgumentException e) {log.warn("ControllerAspect throw IllegalArgumentException, traceId-> {}, invoke {} {} param-> {}; {}", traceId, className, methodName, JSON.toJSONString(argObj), e);// 不确定返回值类型,只能抛出异常throw e;} catch (ServiceException e) {if (Objects.nonNull(typeIndex0)) {// 可以在nacos上配置参数,防止代码异常,导致流程走不下去log.info("ControllerAspect throw ServiceException, actualTypeArguments-> {}", typeIndex0.getTypeName());MultiValueMap<String, String> headers = new HttpHeaders();headers.set("errorMessageTms", URLEncoder.encode(e.getMsg(), StandardCharsets.UTF_8.name()));if (Boolean.class == typeIndex0) {ResponseEntity<Boolean> response = new ResponseEntity(false, headers, HttpStatus.OK);return response;}}throw e;} catch (Exception e) {log.warn("ControllerAspect throw Exception, traceId-> {}, invoke {} {} param-> {}; {}", traceId, className, methodName, JSON.toJSONString(argObj), e);throw e;} catch (Throwable e) {log.warn("ControllerAspect throw Throwable, traceId-> {}, invoke {} {} param-> {}; {}", traceId, className, methodName, JSON.toJSONString(argObj), e);throw e;} finally {MDC.remove(CommonConstant.REQUEST_ID);}}private Type getTypeIndex0(Method method) {Class<?> returnType = method.getReturnType();if (returnType == ResponseEntity.class) {Type type = method.getGenericReturnType();if (type instanceof ParameterizedType) {Type[] actualTypeArguments = ((ParameterizedType) type).getActualTypeArguments();return actualTypeArguments[0];}}return null;}

catch 到 ServiceException 异常后进行处理(将解法1处理方式抽象出来)聚合层定义(和解法1第四点完全相同)

public ResponseEntity<Result<Boolean>> saveTmsBookingOrder(@Valid SaveDTO vo){ResponseEntity<Boolean> response = ***Feign.saveTmsBookingOrder(vo);if (response.getBody()) {return Results.success(Result.success(true));}String errorMsg = "";List<String> errorList = response.getHeaders().get(CommonConstant.ERROR_MSG);if (CollectionUtils.isNotEmpty(errorList)) {try {errorMsg = URLDecoder.decode(errorList.get(0), StandardCharsets.UTF_8.name());} catch (UnsupportedEncodingException e) {log.error("saveTmsBookingOrder fail, param-> {}, UnsupportedEncodingException-> {}", JSON.toJSONString(vo), e);}}return Results.success(Result.error("-1", errorMsg));}

后续直接修改聚合层 Controller 即可或者在聚合层 Controller 层的拦截器中,指定方法名为配置方法时,将 ResponseEntity<Boolean> 转换为 ResponseEntity<Result<Boolean>>,则只需稍微修改聚合层 Controller 即可

// 伪代码@Value("${response.boolean2result.method:method1,method2}")private String resultMethod;List<String> resultMethodList = Arrays.as(StringUtils.split(resultMethod, ","));ResponseEntity<Boolean> response = new ResponseEntity(false, HttpStatus.OK);if(resultMethodList.contains("method1") {// 这里肯定转不成功,需从header头取出信息,封装到response中response = (ResponseEntity<Result<Boolean>>)response;}

疑问❓

原子层和聚合层接口定义都修改为 ResponseEntity<Result<Boolean>>原子层使用最初代码,直接抛异常

throw new ServiceException("si单号:" + siOrders + "已经订舱");

聚合层能正常接收吗(没测试)

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。