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

Spring Boot 3.0.0+枚举参数大小写不敏感处理求助

问题描述

我定义了TimeFramesEnum枚举类,希望在GET请求的URL参数中支持大小写不敏感(可传入小写值)。尝试自定义StringToTimeFramesConverter转换器并通过EnumMappingConfig注册到Spring的FormatterRegistry后,仍收到400 Bad Request错误,提示无法将字符串转换为该枚举类型。

枚举类代码

package org.crea.w4ag_mock.mocker;
import com.fasterxml.jackson.annotation.JsonProperty;
public enum TimeFramesEnum {
    BASELINE,
    PERIOD2_2040,
    PERIOD2_2070
}

自定义转换器代码

package org.crea.w4ag_mock.converter;
import org.apache.commons.lang3.EnumUtils;
import org.apache.commons.lang3.StringUtils;
import org.crea.w4ag_mock.mocker.TimeFramesEnum;
import org.springframework.core.convert.converter.Converter;
public class StringToTimeFramesConverter implements Converter<String, TimeFramesEnum> {
    @Override
    public TimeFramesEnum convert(String source){
        if (StringUtils.isBlank(source)) {
            return null;
        }
        return EnumUtils.getEnum(TimeFramesEnum.class, source.toUpperCase());
    }
}

配置类代码

package org.crea.w4ag_mock.config;
import org.crea.w4ag_mock.converter.StringToTimeFramesConverter;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class EnumMappingConfig implements WebMvcConfigurer {
    @Override
    public void addFormatters(FormatterRegistry registry){
        registry.addConverter(new StringToTimeFramesConverter());
    }
}

RestController代码

package org.crea.w4ag_mock;
import org.crea.w4ag_mock.mocker.*;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
@RestController
public class Water4AgriFoodMockerApplication {
    public static void main(String[] args) {
        SpringApplication.run(Water4AgriFoodMockerApplication.class, args);
    }
    @GetMapping("mock")
    @ResponseBody
    public ResponseEntity<ModelMocker> getMock(
            @RequestParam(required = true) String idName,
            @RequestParam(required = true) String idConfiguration,
            @RequestParam(required = true) String location,
            @RequestParam(required = true) String startTime,
            @RequestParam(name="timeFrames", required = true) TimeFramesEnum timeFrames,
            @RequestParam(required = true) GcmsEnum gcms,
            @RequestParam(required = true) RcpsEnum rcps,
            @RequestParam(required = true) String idCrop,
            @RequestParam(required = true) String idVariety,
            @RequestParam(required = true) String idRotation
    ){
        ModelMocker modelMocker = ModelMocker.builder()
                .idName(idName)
                .idConfiguration(idConfiguration)
                .location(location)
                .startTime(startTime)
                .timeFrames(timeFrames)
                .gcms(gcms)
                .rcps(rcps)
                .idCrop(idCrop)
                .idVariety(idVariety)
                .idRotation(idRotation)
                .developmentStage(ModelMockerRandomValueGenerator.getDevelopmentStageValue())
                .potentialAboveGroundBiomass(ModelMockerRandomValueGenerator.getPotentialAboveGroundBiomassValue())
                .potentialYield(ModelMockerRandomValueGenerator.getPotentialYieldValue())
                .potentialLAI(ModelMockerRandomValueGenerator.getPotentialLAIValue())
                .waterLimitedAboveGroundBiomass(ModelMockerRandomValueGenerator.getWaterLimitedAboveGroundBiomassValue())
                .waterLimitedYield(ModelMockerRandomValueGenerator.getPotentialYieldValue())
                .waterLimitedLAI(ModelMockerRandomValueGenerator.getWaterLimitedLAIValue())
                .build();
        return ResponseEntity.ok(modelMocker);
    }
}

错误信息

{
    "timestamp": "2023-02-27T15:22:06.493+00:00",
    "status": 400,
    "error": "Bad Request",
    "message": "Failed to convert value of type 'java.lang.String' to required type 'org.crea.w4ag_mock.mocker.TimeFramesEnum'; Failed to convert from type [java.lang.String] to type [@org.springframework.web.bind.annotation.RequestParam org.crea.w4ag_mock.mocker.TimeFramesEnum] for value 'baseline'",
    "path": "/mock"
}
解决方案

原因分析

Spring处理枚举类型请求参数时,可能优先使用默认转换器,导致自定义转换器未被触发;另外,若自定义转换器返回null(比如输入值不匹配),Spring会直接抛出转换异常,即便输入值转大写后匹配,也可能因配置未生效导致转换失败。

方案1:确保自定义转换器被正确加载

  • 检查EnumMappingConfig所在包是否在Spring Boot的扫描范围内(主类Water4AgriFoodMockerApplication所在包为org.crea.w4ag_mock,配置类在org.crea.w4ag_mock.config,默认会被扫描)。
  • 在addFormatters方法中添加日志输出,确认该方法是否被调用,验证转换器是否成功注册。

方案2:改用Formatter实现转换

Spring的Formatter更适配Web请求参数的字符串转对象场景,替换转换器为Formatter:

package org.crea.w4ag_mock.converter;

import org.apache.commons.lang3.EnumUtils;
import org.apache.commons.lang3.StringUtils;
import org.crea.w4ag_mock.mocker.TimeFramesEnum;
import org.springframework.format.Formatter;

import java.text.ParseException;
import java.util.Locale;

public class TimeFramesEnumFormatter implements Formatter<TimeFramesEnum> {
    @Override
    public TimeFramesEnum parse(String text, Locale locale) throws ParseException {
        if (StringUtils.isBlank(text)) {
            throw new ParseException("TimeFrames参数不能为空", 0);
        }
        TimeFramesEnum enumValue = EnumUtils.getEnum(TimeFramesEnum.class, text.toUpperCase());
        if (enumValue == null) {
            throw new ParseException("无效的TimeFrames值: " + text, 0);
        }
        return enumValue;
    }

    @Override
    public String print(TimeFramesEnum object, Locale locale) {
        return object.name();
    }
}

修改配置类注册Formatter:

@Override
public void addFormatters(FormatterRegistry registry){
    registry.addFormatter(new TimeFramesEnumFormatter());
}

方案3:在枚举类中添加静态转换方法

Spring会自动识别枚举类中的fromString静态方法用于参数转换,无需额外配置:

package org.crea.w4ag_mock.mocker;

public enum TimeFramesEnum {
    BASELINE,
    PERIOD2_2040,
    PERIOD2_2070;

    public static TimeFramesEnum fromString(String value) {
        if (value == null) {
            throw new IllegalArgumentException("参数值不能为空");
        }
        TimeFramesEnum enumValue = EnumUtils.getEnum(TimeFramesEnum.class, value.toUpperCase());
        if (enumValue == null) {
            throw new IllegalArgumentException("无效的TimeFrames值: " + value);
        }
        return enumValue;
    }
}

方案4:指定参数专属转换器(Spring 5.2+)

直接在@RequestParam注解中指定该参数使用的转换器,避免全局配置冲突:

@RequestParam(name="timeFrames", required = true, converter = StringToTimeFramesConverter.class) TimeFramesEnum timeFrames

内容的提问来源于stack exchange,提问作者Gianni Spear

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 09:02:10