简体   繁体   English

Spring 的 @RequestParam 与枚举

[英]Spring's @RequestParam with Enum

I have this enum :我有这个枚举:

public enum SortEnum {
    asc, desc;
}

That I want to use as a parameter of a rest request :我想用作休息请求的参数:

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

It works fine when I send these requests当我发送这些请求时它工作正常

/events 
/events?sort=asc
/events?sort=desc

But when I send :但是当我发送:

/events?sort=somethingElse

I get a 500 response and this message in the console :我在控制台中收到 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

Is there a way to prevent spring from throwing these exceptions and set the enum to null ?有没有办法防止 spring 抛出这些异常并将枚举设置为 null ?

EDIT编辑

The Strelok's accepted answer works. Strelok 接受的答案有效。 However, I decided to deal with handling the MethodArgumentTypeMismatchException.但是,我决定处理 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);
    }

You can create a custom converter that will return null instead of an exception when an invalid value is supplied.您可以创建一个自定义转换器,当提供无效值时,该转换器将返回null而不是异常。

Something like this:像这样的东西:

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

And a simple converter might look like this:一个简单的转换器可能如下所示:

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
       }
    }
}

If you are using Spring Boot, this is the reason that you should not use WebMvcConfigurationSupport .如果您使用的是 Spring Boot, 这就是您不应该使用WebMvcConfigurationSupport 的原因

The best practice, you should implement interface org.springframework.core.convert.converter.Converter , and with annotation @Component .最佳实践,您应该实现接口org.springframework.core.convert.converter.Converter ,并带有注释@Component Then Spring Boot will auto load all Converter 's bean.然后 Spring Boot 将自动加载所有Converter的 bean。 Spring Boot code 春季启动代码

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

Demo Project 演示项目

you need to do the following您需要执行以下操作

@InitBinder
public void initBinder(WebDataBinder dataBinder) {
    dataBinder.registerCustomEditor(YourEnum.class, new YourEnumConverter());
}

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

The answers provided so far are not complete.到目前为止提供的答案并不完整。 Here is an answer example step-by-step that worked for me:-这是一个对我有用的分步答案示例:-

1st Define the enum in your endpoint signature(subscription type). 1st 在端点签名(订阅类型)中定义枚举。
Example :示例

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,

2nd Define a custom property editor that will be used to translate from String to enum: 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);
        }
    }
}

3rd Register the property editor with the controller: 3rd 向控制器注册属性编辑器:

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

Translations from string to enum should happen perfectly now.从字符串到枚举的转换现在应该完美地发生。

If you have multiple enums then if you follow the other answers you'll end up creating one converter for each one.如果您有多个枚举,那么如果您遵循其他答案,您最终将为每个枚举创建一个转换器。

Here is a solution that works for all enums.这是适用于所有枚举的解决方案。

Converter or PropertyEditorSupport are not appropriate in this case because they don't allow us to know the target class. Converter 或 PropertyEditorSupport 在这种情况下不合适,因为它们不允许我们知道目标类。

In this example I have used the Jackson ObjectMapper, but you could replace this part by a call to the static method via reflection or move the call to values() to the converter.在这个例子中,我使用了 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());
        }
    }
}

and in this case, because I'm using Jackson, the enum class has to have a static method annotated with @JsonCreator so I can map using the value rather than the constant name:在这种情况下,因为我使用的是 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);
    }
}

Instead of returning null it is better to throw an exception.与其返回 null,不如抛出异常。

If you are already implementing WebMvcConfigurer , instead of WebMvcConfigurationSupport , you can add new converter by implementing the method addFormatters如果你已经在实现WebMvcConfigurer ,而不是WebMvcConfigurationSupport ,你可以通过实现 addFormatters 方法来添加新的转换器

  @Override
  public void addFormatters(FormatterRegistry registry) {
    registry.addConverter(new MyCustomEnumConverter());
  }

you can use String instead of SortEnum param您可以使用String而不是SortEnum参数

@RequestParam(name = "sort", required = false) String sort

and convert it using并使用转换它

SortEnum se;
try {
   se = SortEnum.valueOf(source);
} catch(IllegalArgumentException e) {
   se = null;
}

inside of getEvents(...) endpoint method losing elegance but gaining more control over conversion and possible error handling.在 getEvents(...) 端点方法内部失去了优雅,但获得了对转换和可能的错误处理的更多控制。

You can use @JsonValue annotation in the ENUM.您可以在 ENUM 中使用@JsonValue注释。 Check this - https://www.baeldung.com/jackson-serialize-enums检查这个 - https://www.baeldung.com/jackson-serialize-enums

Using some recent Spring version, as per august 2021, the following code is simplest and just works.使用一些最新的 Spring 版本,截至 2021 年 8 月,以下代码最简单且有效。 The only tricky part is the method deserializeByName(), which you need to add to enum, the rest of code is usual.唯一棘手的部分是方法 deserializeByName(),您需要将其添加到枚举中,其余代码是常见的。

public enum WorkplanGenerationMode {

    AppendWorktypes("AppendWorktypes"),
    New("New");

    private String value;

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

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

    @JsonCreator
    public static WorkplanGenerationMode deserializeByName(@JsonProperty("name") String name) {
        return WorkplanGenerationMode.valueOf(name);
    }


}

And then to the following endpoint a string value comes in, and is transformed into correct enum value, and java enum member gets initialised with it.然后到以下端点,一个字符串值进入,并被转换为正确的枚举值,并且 java 枚举成员被它初始化。

public ResponseEntity<List<WorkplanGenerationProgressDTO>> generate(@RequestBody WorkplanFromBaseRequest request) {

NB!注意! WorkplanGenerationMode is a type of any member of WorkplanFromBaseRequest, like this: WorkplanGenerationMode 是 WorkplanFromBaseRequest 的任何成员的类型,如下所示:

@Data
public class WorkplanFromBaseRequest {

    private WorkplanGenerationMode mode;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM