Spring中@RequestBody绑定多态属性时如何按指定子类初始化对象
实现方案
默认情况下Spring解析@RequestBody参数时,是独立通过Jackson反序列化完成的,无法直接获取同请求中的@PathVariable参数,所以需要调整实现逻辑,以下是两种可行方案:
方案1:手动反序列化(快速实现,无额外配置)
直接在Controller层接收原始请求体和类型标识,手动完成对象转换:
class XYZController { // 注入Jackson的序列化工具 @Autowired private ObjectMapper objectMapper; public Resp doThis (@RequestBody JsonNode requestBody, @PathVariable SubType subType) throws JsonProcessingException { // 根据路径变量的枚举值确定B的实际子类类型 Class<? extends B> bClazz = switch (subType) { case C -> C.class; case D -> D.class; }; // 反序列化出对应子类的实例 B b = objectMapper.treeToValue(requestBody.get("b"), bClazz); // 构造A对象 A a = new A(subType, b); // 后续业务逻辑 } }
方案2:自定义参数解析器(可复用,侵入性低)
如果多处有类似需求,可以自定义HandlerMethodArgumentResolver实现自动绑定:
- 自定义注解标记需要联动参数反序列化的入参:
@Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface SubTypeBind { // 路径变量中类型字段的名称 String typeParamName() default "subType"; // 多态属性的字段名 String dataFieldName() default "b"; }
- 实现参数解析器:
@Component public class SubTypeArgumentResolver implements HandlerMethodArgumentResolver { @Autowired private ObjectMapper objectMapper; @Override public boolean supportsParameter(MethodParameter parameter) { // 只处理标记了@SubTypeBind且类型为A的参数 return parameter.hasParameterAnnotation(SubTypeBind.class) && parameter.getParameterType() == A.class; } @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { SubTypeBind anno = parameter.getParameterAnnotation(SubTypeBind.class); // 获取路径变量中的类型值 Map<String, String> uriTemplateVars = (Map<String, String>) webRequest.getAttribute( HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST); SubType subType = SubType.valueOf(uriTemplateVars.get(anno.typeParamName())); // 读取请求体 HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class); String requestBody = StreamUtils.copyToString(request.getInputStream(), StandardCharsets.UTF_8); JsonNode rootNode = objectMapper.readTree(requestBody); // 转换为对应子类实例 Class<? extends B> bClazz = subType == SubType.C ? C.class : D.class; B b = objectMapper.treeToValue(rootNode.get(anno.dataFieldName()), bClazz); // 如果A有其他字段可以在这里从rootNode读取赋值 return new A(subType, b); } }
- 注册参数解析器:
@Configuration public class WebConfig implements WebMvcConfigurer { @Autowired private SubTypeArgumentResolver subTypeArgumentResolver; @Override public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) { resolvers.add(subTypeArgumentResolver); } }
- Controller层直接使用:
class XYZController { public Resp doThis (@SubTypeBind A a, @PathVariable SubType subType) { // 直接使用a对象即可,b属性已经是对应子类的实例 } }
注意事项
- 不要给B类加
@JsonTypeInfo等Jackson自带的多态注解,这类注解需要请求体中携带类型标识,无法适配从路径变量获取类型的场景 - 如果A类存在除
b之外的其他字段,反序列化时可以直接从JsonNode中读取对应值赋值即可
内容的提问来源于stack exchange,提问作者N.Rajal
相关产品推荐
相关产品推荐

