You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot中Jackson模块注册错误?请求参数无法转自定义类

问题

用Spring Boot开发模拟应用,前端发送GET请求,需要把请求参数转换为自定义类CompoundSpecFactory。按照官方文档尝试了两种Jackson自定义配置方式,均未生效:

  • 创建Jackson2ObjectMapperBuilderCustomizer Bean;
  • 创建com.fasterxml.jackson.databind.Module Bean。

两种方式都定义了继承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&parameters=Brighton%20Street&parameters=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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 02:17:35