诡异的Spring @RequestBody驼峰命名字段映射失败为null问题记录
问题
一个非常常规的Spring Controller,代码如下:
import lombok.RequiredArgsConstructor;
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/config")
public class ConfigController {
private final ConfigService configService;
@ApiOperation("新增配置")
@PostMapping("")
public R<ConfigVo> createConfig( /*@Valid*/ @RequestBody ConfigDto dto) {
return R.success(configService.createConfig(dto));
}
}
DTO定义如下:
import io.swagger.annotations.ApiModel;
import lombok.*;
@Data
@AllArgsConstructor
@NoArgsConstructor
@ApiModel("配置")
public class ConfigDto {
@NotBlank
private String appId;
private String json;
private String mark;
}
Postman模拟一次POST请求:
{
"mark": "verifyUserByIdAndName",
"json": "{}",
"appId": "a_15c140390de54948af88ad9b39d1fd2f"
}
代码逻辑不对劲,然后通过断点调试,发现appId字段为null:
WTF???有毒吧。
现象就是驼峰命名的字段解析不出来,映射不上。
@Valid
咨询DeepSeek,一大堆废话。提取到关键词@Valid。
给这个Controller加个@Valid注解,也就是上面的代码片段里被注释的/*@Valid*/
,Postman请求报错:
这个报错是框架类的封装然后返回的。说明Spring(Jackson)确实没有从Postman的request里解析映射出appId字段。
框架类代码片段:
private static final MediaType MEDIA_TYPE = new MediaType("application", "json", StandardCharsets.UTF_8);
/**
* JSR303 表单参数校验失败
* 在Controller层使用@Valid注解
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler({MethodArgumentNotValidException.class})
public ResponseEntity<R<?>> handleMethodArgumentNotValidException(MethodArgumentNotValidException e,
HttpServletRequest request) {
this.logWarn(e, request, "不合法的参数异常");
InvalidField invalidField = getInvalidField(e.getBindingResult());
R<InvalidField> invalidFieldR = new R<>(HttpStatus.INTERNAL_SERVER_ERROR.value(), "不合法的参数异常", invalidField);
return createResponseEntity(HttpStatus.BAD_REQUEST, invalidFieldR);
}
private void logWarn(Exception e, HttpServletRequest request, String... msg) {
String template = "[Web][有Warn被抛出] >> Warn类=[%s], URI=[%s], 消息=[%s], Warn=[%s]";
log.warn(String.format(template, e.getClass().getName(), request.getRequestURI(),
ArrayUtil.isEmpty(msg) ? e.getMessage() : ArrayUtil.join(msg, ","), ExceptionUtil.stacktraceToString(e)));
}
private InvalidField getInvalidField(BindingResult bindingResult) {
if (bindingResult == null) {
return null;
}
FieldError fieldError = bindingResult.getFieldError();
if (fieldError == null) {
return null;
}
return InvalidField.builder()
.fieldName(fieldError.getField())
.message(fieldError.getDefaultMessage())
.build();
}
protected static ResponseEntity<R<?>> createResponseEntity(HttpStatus httpStatus, R<?> body) {
return ResponseEntity.status(httpStatus.value()).contentType(MEDIA_TYPE).body(body);
}
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Data
public static class InvalidField {
private String fieldName;
private String message;
}
断点调试截图:
workaround
继续询问DeepSeek,又是一大堆废话,提取到关键词@JsonProperty。
一开始并没有关注这个,因为这个注解的使用场景是字段命名不匹配。
试试吧,增加@JsonProperty("appId")
,也就是
public class ConfigDto {
@NotBlank
@JsonProperty("appId")
private String appId;
}
能解析到appId字段。
但是!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
这两个地方的命名,明明是一模一样的啊。
分析
为啥呢?
Lombok
不是Lombok的问题,其他字段都没有问题,再说appId是一个非常常规的驼峰命名,又不是isSuccess
或cAppId
这种不规范命名。
编译后的代码如下,没有问题
调试
在DTO里的字段加断点调试,
在Controller层加断点调试说明@JsonProperty生效:
为啥呢??
一个比较相似的blog,不过他这篇里的命名不太规范。