诡异的Spring @RequestBody驼峰命名字段映射失败为null问题记录

发布于:2025-02-10 ⋅ 阅读:(61) ⋅ 点赞:(0)

问题

一个非常常规的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是一个非常常规的驼峰命名,又不是isSuccesscAppId这种不规范命名。

编译后的代码如下,没有问题
在这里插入图片描述

调试

在DTO里的字段加断点调试,
在这里插入图片描述
在Controller层加断点调试说明@JsonProperty生效:
在这里插入图片描述

为啥呢??

一个比较相似的blog,不过他这篇里的命名不太规范。

参考