Spring Boot中Jackson模块注册错误?请求参数无法转自定义类
问题
用Spring Boot开发模拟应用,前端发送GET请求,需要把请求参数转换为自定义类CompoundSpecFactory。按照官方文档尝试了两种Jackson自定义配置方式,均未生效:
- 创建
Jackson2ObjectMapperBuilderCustomizerBean; - 创建
com.fasterxml.jackson.databind.ModuleBean。
两种方式都定义了继承JsonDeserializer的自定义反序列化器,并添加到配置中,但实际请求参数并未转换为目标类,打印对象为null。第二种方案中反序列化器的deserialize()方法日志未输出,推测模块未注册,但找不到原因。
相关代码如下:
自定义Module代码
@Component public class CompoundSpecModule<B extends Building> extends SimpleDeserializers { private static final long serialVersionUID = 1L; public CompoundSpecModule() { this.addDeserializer(CompoundSpecFactory.class, new CriteriaDeserializer<B>()); } }
自定义反序列化器代码
public class CriteriaDeserializer<B extends Building> extends StdDeserializer<CompoundSpecFactory<B>> { private static final long serialVersionUID = 1L; protected CriteriaDeserializer() { super(CompoundSpecFactory.class); } @Override public Class<?> handledType() { return CompoundSpecFactory.class; } @Override public CompoundSpecFactory<B> deserialize(JsonParser p, DeserializationContext ctx) throws IOException, JacksonException { System.out.println("deserialize has been called"); JsonNode root = p.getCodec().readTree(p); List<String> criterionTypes = this.getCriterionTypes(root.get("criterionType")); List<String> dimensions = this.getDimensions(root.get("dimension")); List<List<String>> parameters = this.getParameters(root.get("parameters")); ArrayList<BuildingSpec<B>> specs = this.getSpecifications(criterionTypes, dimensions, parameters); return new CompoundSpecFactory<B>(specs); } // 省略其他方法 }
控制器代码
@RestController @RequestMapping("/building") public class BuildingController { private BuildingService buildingService; public BuildingController(BuildingService bs) { buildingService = bs; } @GetMapping("/getForRentBuildings") public List<ForRentBuilding> getForRentBuildings(CompoundSpecFactory<ForRentBuilding> compSpecFactory){ return buildingService.getForRentBuildings(compSpecFactory); } }
请求URL示例
http://localhost:4200/buildings/building/getForRentBuildings?criterionType=equals&criterionType=equals&dimension=street&dimension=city¶meters=Brighton%20Street¶meters=London
目标转换类CompoundSpecFactory代码
public class CompoundSpecFactory<B extends Building>{ private Specification<B> compoundSpec; public CompoundSpecFactory() { } @SuppressWarnings("unchecked") public CompoundSpecFactory(BuildingSpec<B> ...buildingSpecs) { compoundSpec = buildCompoundSpecification(buildingSpecs); } public CompoundSpecFactory(List<BuildingSpec<B>>buildingSpecs) { compoundSpec = buildCompoundSpecification(buildingSpecs); } @SuppressWarnings("unchecked") public Specification<B> buildCompoundSpecification(BuildingSpec<B> ...buildingSpecs) { Specification<B> res = Specification.where(null); for(BuildingSpec<B> bs: buildingSpecs) { res = res.and(bs); } return res; } public Specification<B> buildCompoundSpecification(List<BuildingSpec<B>>buildingSpecs) { Specification<B> res = Specification.where(null); for(BuildingSpec<B> bs: buildingSpecs) { res = res.and(bs); } return res; } public Specification<B> getCompoundSpec() { return compoundSpec; } }
需求说明:前端传入的criterionType、dimension、parameters需要转换为Specification,再合并为复合规则,比如示例URL要生成“建筑的city等于'London'且street等于'Brighton Street'”的复合规则。
解决方案
核心问题:Jackson反序列化不处理GET查询参数
你之前的思路完全错误——Jackson的JsonDeserializer是用来处理JSON格式请求体(比如POST请求的@RequestBody)的,而GET请求的查询参数是通过Servlet的请求参数机制传递的,Spring处理这类参数用的是Converter或PropertyEditor,和Jackson无关,所以你的自定义Module和Jackson配置根本不会被触发。
正确实现方式:自定义Spring参数转换逻辑
方式一:控制器内直接绑定参数并转换(最直观)
修改控制器方法,先绑定所有请求参数,再手动转换为CompoundSpecFactory:
@RestController @RequestMapping("/building") public class BuildingController { private BuildingService buildingService; public BuildingController(BuildingService bs) { buildingService = bs; } @GetMapping("/getForRentBuildings") public List<ForRentBuilding> getForRentBuildings( @RequestParam List<String> criterionType, @RequestParam List<String> dimension, @RequestParam List<String> parameters) { // 校验参数数量一致性 if (criterionType.size() != dimension.size() || dimension.size() != parameters.size()) { throw new IllegalArgumentException("所有参数列表长度必须一致"); } List<BuildingSpec<ForRentBuilding>> specs = new ArrayList<>(); for (int i = 0; i < criterionType.size(); i++) { String type = criterionType.get(i); String dim = dimension.get(i); String param = parameters.get(i); // 根据条件类型创建对应的Specification BuildingSpec<ForRentBuilding> spec = switch (type) { case "equals" -> (root, query, cb) -> cb.equal(root.get(dim), param); // 可扩展其他条件类型,比如like、greaterThan等 default -> throw new IllegalArgumentException("不支持的条件类型:" + type); }; specs.add(spec); } CompoundSpecFactory<ForRentBuilding> compSpecFactory = new CompoundSpecFactory<>(specs); return buildingService.getForRentBuildings(compSpecFactory); } }
方式二:注册全局Converter实现自动转换
如果需要在多个控制器中复用转换逻辑,可以创建全局Converter并注册到Spring的ConversionService:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.convert.converter.Converter; import org.springframework.web.context.request.WebRequest; import java.util.ArrayList; import java.util.List; @Configuration public class WebConfig { @Bean public Converter<WebRequest, CompoundSpecFactory<ForRentBuilding>> compoundSpecConverter() { return webRequest -> { // 从请求中获取所有参数列表 List<String> criterionTypes = webRequest.getParameterValues("criterionType") != null ? List.of(webRequest.getParameterValues("criterionType")) : List.of(); List<String> dimensions = webRequest.getParameterValues("dimension") != null ? List.of(webRequest.getParameterValues("dimension")) : List.of(); List<String> parameters = webRequest.getParameterValues("parameters") != null ? List.of(webRequest.getParameterValues("parameters")) : List.of(); // 校验参数长度 if (criterionTypes.size() != dimensions.size() || dimensions.size() != parameters.size()) { throw new IllegalArgumentException("所有参数列表长度必须一致"); } List<BuildingSpec<ForRentBuilding>> specs = new ArrayList<>(); for (int i = 0; i < criterionTypes.size(); i++) { String type = criterionTypes.get(i); String dim = dimensions.get(i); String param = parameters.get(i); BuildingSpec<ForRentBuilding> spec = switch (type) { case "equals" -> (root, query, cb) -> cb.equal(root.get(dim), param); // 扩展其他条件类型 default -> throw new IllegalArgumentException("不支持的条件类型:" + type); }; specs.add(spec); } return new CompoundSpecFactory<>(specs); }; } }
注册后,控制器中的CompoundSpecFactory<ForRentBuilding>参数会被自动转换,无需手动处理。
总结
- Jackson反序列化仅针对JSON请求体,不处理GET查询参数;
- 处理GET查询参数的自定义类型转换,必须使用Spring的
Converter或直接在控制器中手动转换; - 优先选择控制器内手动转换的方式,逻辑更直观;需要复用时再使用全局Converter。
内容的提问来源于stack exchange,提问作者the-frank

