Spring的@RequestParam与Enum

Ant*_*tin 52 java spring spring-boot spring-web

我有这个枚举:

public enum SortEnum {
    asc, desc;
}
Run Code Online (Sandbox Code Playgroud)

我想用作休息请求的参数:

@RequestMapping(value = "/events", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public List<Event> getEvents(@RequestParam(name = "sort", required = false) SortEnum sort) {
Run Code Online (Sandbox Code Playgroud)

我发送这些请求时工作正常

/events 
/events?sort=asc
/events?sort=desc
Run Code Online (Sandbox Code Playgroud)

但是当我发送时:

/events?sort=somethingElse
Run Code Online (Sandbox Code Playgroud)

我在控制台中得到500响应和此消息:

2016-09-29 17:20:51.600 DEBUG 5104 --- [  XNIO-2 task-6] com.myApp.aop.logging.LoggingAspect   : Enter: com.myApp.web.rest.errors.ExceptionTranslator.processRuntimeException() with argument[s] = [org.springframework.web.method.annotation.MethodArgumentTypeMismatchException: Failed to convert value of type [java.lang.String] to required type [com.myApp.common.SortEnum]; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [@org.springframework.web.bind.annotation.RequestParam com.myApp.common.SortEnum] for value 'somethingElse'; nested exception is java.lang.IllegalArgumentException: No enum constant com.myApp.common.SortEnum.somethingElse]
2016-09-29 17:20:51.600 DEBUG 5104 --- [  XNIO-2 task-6] com.myApp.aop.logging.LoggingAspect   : Exit: com.myApp.web.rest.errors.ExceptionTranslator.processRuntimeException() with result = <500 Internal Server Error,com.myApp.web.rest.errors.ErrorVM@1e3343c9,{}>
2016-09-29 17:20:51.601  WARN 5104 --- [  XNIO-2 task-6] .m.m.a.ExceptionHandlerExceptionResolver : Resolved exception caused by Handler execution: org.springframework.web.method.annotation.MethodArgumentTypeMismatchException: Failed to convert value of type [java.lang.String] to required type [com.myApp.common.SortEnum]; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [@org.springframework.web.bind.annotation.RequestParam com.myApp.common.SortEnum] for value 'somethingElse'; nested exception is java.lang.IllegalArgumentException: No enum constant com.myApp.common.SortEnum.somethingElse
Run Code Online (Sandbox Code Playgroud)

有没有办法阻止spring抛出这些异常并将枚举设置为null?

编辑

Strelok接受的答案有效.但是,我决定处理MethodArgumentTypeMismatchException.

@ControllerAdvice
public class ExceptionTranslator {

    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    @ResponseBody
    public ResponseEntity<Object> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e) {
        Class<?> type = e.getRequiredType();
        String message;
        if(type.isEnum()){
            message = "The parameter " + e.getName() + " must have a value among : " + StringUtils.join(type.getEnumConstants(), ", ");
        }
        else{
            message = "The parameter " + e.getName() + " must be of type " + type.getTypeName();
        }
        return buildResponse(HttpStatus.UNPROCESSABLE_ENTITY, message);
    }
Run Code Online (Sandbox Code Playgroud)

Str*_*lok 48

您可以创建一个自定义转换器,null当提供无效值时,它将返回而不是异常.

像这样的东西:

@Configuration
public class MyConfig extends WebMvcConfigurationSupport {
   @Override
   public FormattingConversionService mvcConversionService() {
       FormattingConversionService f = super.mvcConversionService();
       f.addConverter(new MyCustomEnumConverter());
       return f;
   }
}
Run Code Online (Sandbox Code Playgroud)

一个简单的转换器可能看起来像这样:

public class MyCustomEnumConverter implements Converter<String, SortEnum> {
    @Override
    public SortEnum convert(String source) {
       try {
          return SortEnum.valueOf(source);
       } catch(Exception e) {
          return null; // or SortEnum.asc
       }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • @CharlesWood不,那是一个`org.springframework.core.convert.converter.Converter` (6认同)
  • 如果您希望对所有端点全局执行此行为,则这是正确的答案.如果你只想为你的一个控制器做这个行为,那么satish chennupati有正确的解决方案. (4认同)
  • 请注意,扩展WebMvcConfigurationSupport可能会有副作用,例如,与spring-boot-starter-actuator一起使用会导致重复的bean冲突。一种解决方案是使用@Autowired获取FormattingConversionService bean,并将转换器添加到其中。 (3认同)
  • 那是`com.fasterxml.jackson.databind.util.Converter`吗? (2认同)

Cnf*_*nfn 24

如果您使用的是Spring Boot,这就是您不应该使用的原因WebMvcConfigurationSupport。

最佳做法是,您应该实现interface org.springframework.core.convert.converter.Converter,并带有批注@Component。然后Spring Boot将自动加载all Converter的bean。Spring Boot代码

@Component
public class GenderEnumConverter implements Converter<String, GenderEnum> {
    @Override
    public GenderEnum convert(String value) {
        return GenderEnum.of(Integer.valueOf(value));
    }
}
Run Code Online (Sandbox Code Playgroud)

示范项目


sat*_*ati 20

你需要做以下事情

@InitBinder
public void initBinder(WebDataBinder dataBinder) {
    dataBinder.registerCustomEditor(YourEnum.class, new YourEnumConverter());
}
Run Code Online (Sandbox Code Playgroud)

请参阅以下内容:https://machiel.me/post/java-enums-as-request-parameters-in-spring-4/


小智 5

到目前为止提供的答案并不完整。这是一个对我有用的分步答案示例:-

1st 在端点签名(订阅类型)中定义枚举。
示例:

public ResponseEntity v1_getSubscriptions(@PathVariable String agencyCode,
                                          @RequestParam(value = "uwcompany", required = false) String uwCompany,
                                          @RequestParam(value = "subscriptiontype", required = false) SubscriptionType subscriptionType,
                                          @RequestParam(value = "alert", required = false) String alert,
Run Code Online (Sandbox Code Playgroud)

2nd 定义一个自定义属性编辑器,用于从字符串转换为枚举:

import java.beans.PropertyEditorSupport;

public class SubscriptionTypeEditor extends PropertyEditorSupport {

    public void setAsText(String text) {
        try {
            setValue(SubscriptionType.valueOf(text.toUpperCase()));
        } catch (Exception ex) {
            setValue(null);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

3rd 向控制器注册属性编辑器:

@InitBinder ("subscriptiontype")
public void initBinder(WebDataBinder dataBinder) {
    dataBinder.registerCustomEditor(SubscriptionType.class, new SubscriptionTypeEditor());
}
Run Code Online (Sandbox Code Playgroud)

从字符串到枚举的转换现在应该完美地发生。


Con*_*ger 5

如果您有多个枚举,那么如果您遵循其他答案,您最终将为每个枚举创建一个转换器。

这是适用于所有枚举的解决方案。

Converter 或 PropertyEditorSupport 在这种情况下不合适,因为它们不允许我们知道目标类。

在这个例子中,我使用了 Jackson ObjectMapper,但你可以通过反射调用静态方法来替换这部分,或者将调用 values() 移动到转换器。

@Component
public class JacksonEnumConverter implements GenericConverter {

    private ObjectMapper mapper;

    private Set<ConvertiblePair> set;

    @Autowired
    public JacksonEnumConverter(ObjectMapper mapper) {
        set = new HashSet<>();
        set.add(new ConvertiblePair(String.class, Enum.class));
        this.mapper = mapper;
    }

    @Override
    public Set<ConvertiblePair> getConvertibleTypes() {
        return set;
    }

    @Override
    public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
        if (source == null) {
            return null;
        }
        try {
            return mapper.readValue("\"" + source + "\"", targetType.getType());
        } catch (IOException e) {
            throw new InvalidFieldException(targetType.getName(),source.toString());
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,因为我使用的是 Jackson,所以枚举类必须有一个用 @JsonCreator 注释的静态方法,以便我可以使用值而不是常量名称进行映射:

public enum MyEnum {

    VAL_1("val-1"), VAL_2("val-2");

    private String value;

    MyEnum(String value) {
        this.value = value;
    }

    @JsonValue
    public String getValue() {
        return value;
    }

    @JsonCreator(mode = JsonCreator.Mode.DELEGATING)
    public static MyEnum fromValue(String value) {
        for (MyEnum e : values()) {
            if (e.value.equalsIgnoreCase(value)) {
                return e;
            }
        }
        throw new InvalidFieldException("my-enum", value);
    }
}
Run Code Online (Sandbox Code Playgroud)

与其返回 null,不如抛出异常。

  • 我正在寻找一种解决方案,最终不会有大量转换器。谢谢! (2认同)