Spring Boot @PostMapping如何在请求体映射前检测空字段区分两类请求
Spring Boot区分请求体未传字段与显式传null的实现方案
核心原理
Spring Boot默认使用Jackson完成JSON到Java对象的反序列化,只有请求JSON中明确存在的key,才会触发对应字段的赋值逻辑,未出现的key不会对Java对象的对应字段做任何操作,我们可以利用这个特性区分两种场景。
方案1:添加字段存在性标记位(最易实现,无额外依赖)
通过新增标记位记录对应字段的setter是否被调用,实现场景区分:
@Entity @Table(name="Users") public class User { @Id @JsonProperty("USER") @Column(name = "USER") private String userId; @JsonProperty("FIRST_NAME") @Column(name = "FIRST_NAME") private String firstname; @JsonProperty("LAST_NAME") @Column(name = "LAST_NAME") private String lastname; @JsonProperty("AGE") @Column(name = "AGE") private String age; // 新增存在性标记,默认false,@Transient避免持久化到数据库 @Transient private boolean lastNamePresent = false; @Transient private boolean agePresent = false; // 重写对应setter,只要调用就标记为字段已传 @JsonProperty("LAST_NAME") public void setLastname(String lastname) { this.lastNamePresent = true; this.lastname = lastname; } @JsonProperty("AGE") public void setAge(String age) { this.agePresent = true; this.age = age; } // 新增判断用getter public boolean isLastNamePresent() { return lastNamePresent; } public boolean isAgePresent() { return agePresent; } // 其他原有getter、setter保持不变 public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } public String getFirstname() { return firstname; } public void setFirstname(String firstname) { this.firstname = firstname; } public String getLastname() { return lastname; } public String getAge() { return age; } }
Controller层判断逻辑:
@PostMapping("/post") public ResponseEntity<User> insertFromBody(@Valid @RequestBody User user) { if (user.isLastNamePresent()) { if (user.getLastname() == null) { // 第二种场景:显式传LAST_NAME为null } } else { // 第一种场景:未携带LAST_NAME字段 } // AGE字段判断逻辑同理 // 调用业务逻辑 }
方案2:使用JDK8+ Optional类型(代码更简洁)
将需要判断的字段定义为Optional类型,Jackson反序列化时会自动区分:未传字段的Optional对象本身为null,显式传null的字段会被序列化为Optional.empty():
@JsonProperty("LAST_NAME") @Column(name = "LAST_NAME") private Optional<String> lastname; @JsonProperty("AGE") @Column(name = "AGE") private Optional<String> age;
判断逻辑:
if (user.getLastname() == null) { // 第一种场景:未携带LAST_NAME字段 } else if (user.getLastname().isEmpty()) { // 第二种场景:显式传LAST_NAME为null } else { // 传了非null的LAST_NAME值 String lastNameVal = user.getLastname().get(); }
注意:如果需要将Optional字段持久化到数据库,需要JPA 2.2及以上版本支持,低版本JPA需额外配置类型转换器。
方案3:全局请求拦截(无需修改实体类)
如果不能修改User实体类,可以通过RequestBodyAdvice全局拦截请求,提前解析原始JSON判断字段是否存在,存入请求上下文供Controller使用:
@ControllerAdvice public class UserRequestAdvice implements RequestBodyAdvice { private final ObjectMapper objectMapper = new ObjectMapper(); @Override public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) { // 只处理参数为User类型的请求 return User.class.isAssignableFrom(methodParameter.getParameterType()); } @Override public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException { // 读取原始请求体 String body = StreamUtils.copyToString(inputMessage.getBody(), StandardCharsets.UTF_8); JsonNode jsonNode = objectMapper.readTree(body); // 将字段存在性标记存入请求属性 RequestAttributes attributes = RequestContextHolder.getRequestAttributes(); if (attributes != null) { attributes.setAttribute("LAST_NAME_EXIST", jsonNode.has("LAST_NAME"), RequestAttributes.SCOPE_REQUEST); attributes.setAttribute("AGE_EXIST", jsonNode.has("AGE"), RequestAttributes.SCOPE_REQUEST); } // 构造新的请求流返回,不影响后续反序列化 return new MappingJacksonInputMessage(new ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8)), inputMessage.getHeaders()); } @Override public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) { return body; } @Override public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) { return body; } }
Controller层取值逻辑:
@PostMapping("/post") public ResponseEntity<User> insertFromBody(@Valid @RequestBody User user) { RequestAttributes attributes = RequestContextHolder.getRequestAttributes(); boolean lastNameExist = (boolean) attributes.getAttribute("LAST_NAME_EXIST", RequestAttributes.SCOPE_REQUEST); boolean ageExist = (boolean) attributes.getAttribute("AGE_EXIST", RequestAttributes.SCOPE_REQUEST); if (lastNameExist && user.getLastname() == null) { // 第二种场景:显式传LAST_NAME为null } else if (!lastNameExist) { // 第一种场景:未携带LAST_NAME字段 } // AGE字段判断逻辑同理 }
内容的提问来源于stack exchange,提问作者PrasadM96
相关产品推荐
相关产品推荐

