96 votes

@RequestParam de Spring avec Enum

J'ai cet enum :

public enum SortEnum {
    asc, desc;
}

que je veux utiliser comme paramètre d'une demande de repos :

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

Cela fonctionne bien lorsque j'envoie ces requêtes

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

Mais quand j'envoie :

/events?sort=somethingElse

J'obtiens une réponse 500 et ce message dans la console :

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

Existe-t-il un moyen d'empêcher Spring de lancer ces exceptions et de définir l'enum comme nulle ?

EDIT

La réponse acceptée par Strelok fonctionne. Cependant, j'ai décidé de m'occuper de la gestion de l'exception 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);
    }

0 votes

J'ai vérifié la signification de 422 et il est dit : "la syntaxe de l'entité de la demande est correcte", ce que je ne pense pas être le cas si la chaîne ne correspond pas à l'enum.

0 votes

Le code d'erreur approprié devrait être 400 ("Bad Request")

84voto

Cnfn Points 83

Si vous utilisez Spring Boot, c'est la raison que vous ne devez pas utiliser WebMvcConfigurationSupport .

La meilleure pratique consiste à implémenter l'interface org.springframework.core.convert.converter.Converter et avec annotation @Component . Ensuite, Spring Boot chargera automatiquement tous les Converter Le haricot. Code Spring Boot

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

Projet de démonstration

0 votes

C'est la réponse la plus concise ! Dans la dernière version de Spring Boot (ici 2.5.2), cela se fait différemment : github.com/spring-projects/spring-boot/blob/v2.5.2/ github.com/spring-projects/spring-boot/blob/v2.5.2/

66voto

Strelok Points 18453

Vous pouvez créer un convertisseur personnalisé qui retournera null au lieu d'une exception lorsqu'une valeur invalide est fournie.

Quelque chose comme ça :

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

Et un convertisseur simple pourrait ressembler à ceci :

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

5 votes

C'est la bonne réponse si vous voulez que ce comportement soit global pour tous les points d'extrémité. Si vous voulez ce comportement pour votre seul contrôleur, alors Satish Chennupati a trouvé la bonne solution.

1 votes

Après avoir fait cela, d'une manière ou d'une autre, mon point d'accès oauth2 a été perturbé et l'utilisateur n'a pas pu s'authentifier.

2 votes

Est-ce que com.fasterxml.jackson.databind.util.Converter ?

27voto

satish chennupati Points 1481

Vous devez faire ce qui suit

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

se référer à ce qui suit : https://machiel.me/post/java-enums-as-request-parameters-in-spring-4/

10voto

Si vous avez plusieurs enums, si vous suivez les autres réponses, vous finirez par créer un convertisseur pour chacun d'eux.

Voici une solution qui fonctionne pour tous les enums.

Converter ou PropertyEditorSupport ne sont pas appropriés dans ce cas car ils ne nous permettent pas de connaître la classe cible.

Dans cet exemple, j'ai utilisé l'ObjectMapper de Jackson, mais vous pourriez remplacer cette partie par un appel à la méthode statique par réflexion ou déplacer l'appel à values() vers le convertisseur.

@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());
        }
    }
}

et dans ce cas, parce que j'utilise Jackson, la classe de l'enum doit avoir une méthode statique annotée avec @JsonCreator afin que je puisse utiliser la valeur plutôt que le nom de la constante :

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

Au lieu de renvoyer null, il est préférable de lancer une exception.

2 votes

Je cherchais justement une solution pour ne pas me retrouver avec une tonne de convertisseurs. Merci !

1 votes

J'ai ajouté un truc sympa : une solution de repli : Si je ne peux pas le mapper avec JSON, je me rabats sur la recherche de la valeur de l'enum.

0 votes

Ne pas renvoyer null. Retournez plutôt un enum par défaut, par exemple UNKNOWN, ou lancez une exception.

6voto

user3862533 Points 41

Les réponses fournies jusqu'à présent ne sont pas complètes. Voici un exemple de réponse étape par étape qui a fonctionné pour moi :-.

1. Définissez l'enum dans la signature de votre endpoint (type d'abonnement).
Ejemplo :

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,

2. Définir un éditeur de propriété personnalisé qui sera utilisé pour traduire de String à enum :

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

3. Enregistrez l'éditeur de propriétés avec le contrôleur :

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

Les traductions de la chaîne de caractères à l'enum devraient se faire parfaitement maintenant.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X