Spring MVC:GET请求中泛型包装类型参数反序列化异常问题
解决Spring Boot泛型包装类GET参数类型转换问题
问题根源
- Java泛型类型擦除导致运行时
Wrapper<T>被视为Wrapper<Object>,Spring无法识别泛型参数的实际类型(比如LocalDate)。 - GET请求的参数绑定依赖Spring的
DataBinder机制,而非Jackson JSON反序列化,所以Jackson的泛型配置对这类场景无效。
解决方案
方案1:自定义GenericConverter(通用适配所有泛型类型)
创建通用转换器,自动识别Wrapper<T>的泛型参数并完成类型转换:
- 定义泛型包装类:
public class Wrapper<T> { private T value; // getter、setter、构造器 public T getValue() { return value; } public void setValue(T value) { this.value = value; } }
- 实现GenericConverter:
import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.converter.GenericConverter; import org.springframework.format.support.DefaultFormattingConversionService; import java.util.Set; public class WrapperConverter implements GenericConverter { private final DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService(); @Override public Set<ConvertiblePair> getConvertibleTypes() { return Set.of(new ConvertiblePair(String.class, Wrapper.class)); } @Override public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { if (source == null) return null; // 获取Wrapper的泛型参数类型 TypeDescriptor valueType = targetType.getElementTypeDescriptor(Wrapper.class.getDeclaredField("value")); // 利用Spring内置转换服务完成类型转换 Object convertedValue = conversionService.convert(source.toString(), sourceType, valueType); Wrapper<Object> wrapper = new Wrapper<>(); wrapper.setValue(convertedValue); return wrapper; } }
- 注册转换器到Spring容器:
import org.springframework.context.annotation.Configuration; import org.springframework.format.FormatterRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addFormatters(FormatterRegistry registry) { registry.addConverter(new WrapperConverter()); } }
方案2:创建泛型子类(简单直接,适合少量类型)
为每个需要的泛型类型创建Wrapper的子类,让Spring能明确识别目标类型:
// 针对LocalDate的包装子类 public class LocalDateWrapper extends Wrapper<LocalDate> {}
在DTO中使用子类替代泛型类:
public class DemoDto { private LocalDateWrapper dateValue; // getter、setter public LocalDateWrapper getDateValue() { return dateValue; } public void setDateValue(LocalDateWrapper dateValue) { this.dateValue = dateValue; } }
方案3:@InitBinder注册PropertyEditor(单个Controller专用)
如果仅在特定Controller中使用,可通过@InitBinder注册自定义编辑器:
import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.annotation.InitBinder; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RestController; import java.beans.PropertyEditorSupport; import java.time.LocalDate; import java.time.format.DateTimeFormatter; @RestController public class DemoController { @InitBinder public void initBinder(WebDataBinder binder) { binder.registerCustomEditor(Wrapper.class, new PropertyEditorSupport() { @Override public void setAsText(String text) throws IllegalArgumentException { LocalDate date = LocalDate.parse(text, DateTimeFormatter.ISO_LOCAL_DATE); Wrapper<LocalDate> wrapper = new Wrapper<>(); wrapper.setValue(date); setValue(wrapper); } }); } @GetMapping("/demo") public String demo(@ModelAttribute DemoDto dto) { LocalDate date = dto.getDateValue().getValue(); return "Parsed Date: " + date; } }
内容的提问来源于stack exchange,提问作者Florian Patzl
相关产品推荐
相关产品推荐

