自定义错误处理程序带有ENUM类型的spring Boot @RequestBody DTO类(使用验证器方法1更新,使用验证器方法2更新)

qlvxas9a  于 2023-06-22  发布在  Spring
关注(0)|答案(2)|浏览(93)

我试图弄清楚如何使用枚举类作为输入参数来处理错误。我想实现的是某种验证,它返回一条错误消息,突出显示所有允许的类别。

@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@Builder
public class UserDTO {

    private String name;
    private int age;
    private GenderTypeEnum gender;
    private List<HobbyEnum> hobby;

}

和/或

public enum GenderTypeEnum {

    MALE("male"),
    FEMALE("female");

    private final String value;

    private GenderTypeEnum(String value) {
        this.value = value;
    }

    @JsonValue
    public String getGender(){
        return this.value;
    }

    @JsonCreator
    public static GenderTypeEnum fromValues(String value) {
        for (GenderTypeEnum category : GenderTypeEnum.values()) {
            if (category.getGender().equalsIgnoreCase(value)) {
                return category;
            }
        }
        throw new IllegalArgumentException(
                "Unknown enum type " + value + ", Allowed values are " + Arrays.toString(values()));
    }
}

和/或

public enum HobbyEnum {

    BIKE("bike"),
    CLIMB("climb"),
    TENNIS("tennis");

    private final String value;

    private HobbyEnum(String value) {
        this.value = value;
    }

    @JsonValue
    public String getGender(){
        return this.value;
    }

    @JsonCreator
    public static HobbyEnum fromValues(String value) {
        for (HobbyEnum category : HobbyEnum.values()) {
            if (category.getGender().equalsIgnoreCase(value)) {
                return category;
            }
        }
        throw new IllegalArgumentException(
                "Unknown enum type " + value + ", Allowed values are " + Arrays.toString(values()));
    }

}

和/或

@RestController
@RequestMapping("api/v1")
public class UserController {

    @GetMapping(path = "/user")
    @ResponseBody
    public ResponseEntity<?> getUser(

            @RequestBody
            UserDTO user

    ){
        return ResponseEntity
                .ok()
                .body(user);

    }
}

它当前返回以下类型的错误消息(例如,将male错放为malex

在Sping Boot 控制台上,我得到了以下消息:

2023-06-08T18:26:34.233+02:00  WARN 28248 --- [nio-8080-exec-3] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot construct instance of `com.example.enumhandler.enumerator.GenderTypeEnum`, problem: Unknown enum type malex, Allowed values are [MALE, FEMALE]]

我用下面的方法实现了一个验证器,但是没有得到希望的消息(https://www.baeldung.com/javax-validations-enums)。

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>6.0.10.Final</version>
</dependency>

和/或

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = GenderTypeEnumSubSetValidator.class)
public @interface GenderTypeEnumSubset {
    GenderTypeEnum[] anyOf();
    String message() default "must be any of {anyOf}";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

和/或

package com.example.enumhandler.validator;

import com.example.enumhandler.enumerator.GenderTypeEnum;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.Arrays;

public class GenderTypeEnumSubSetValidator
        implements ConstraintValidator<GenderTypeEnumSubset, GenderTypeEnum> {

    private GenderTypeEnum[] subset;

    @Override
    public void initialize(GenderTypeEnumSubset constraint) {
        this.subset = constraint.anyOf();
    }

    @Override
    public boolean isValid(GenderTypeEnum value, ConstraintValidatorContext context) {
        return value == null || Arrays.asList(subset).contains(value);
    }

}

并更新了类UserDTO

@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@Builder
public class UserDTO {

    private String name;
    private int age;
    @GenderTypeEnumSubset(anyOf = {GenderTypeEnum.MALE, GenderTypeEnum.FEMALE})
    private GenderTypeEnum gender;
    private List<HobbyEnum> hobby;

}

但最后我得到了和以前一样的错误消息,没有改进:

我也找到了这个解决方案[https://medium.com/@vandernobrel/creating-custom-bean-validation-annotations-2c21f5e24b06][4],我尝试实现:

import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = GenderTypeEnumSubSetValidator.class)
public @interface GenderTypeEnumSubset {
    Class<? extends Enum<?>> enumClass();
    String message() default "must be any of {anyOf}";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

和/或

import com.example.enumhandler.enumerator.GenderTypeEnum;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

    public class GenderTypeEnumSubSetValidator
            implements ConstraintValidator<GenderTypeEnumSubset, GenderTypeEnum> {
    
        private List<String> acceptedValues;
    
        @Override
        public void initialize(GenderTypeEnumSubset constraint) {
            acceptedValues = Stream.of(constraint.enumClass().getEnumConstants())
                    .map(Enum::name)
                    .collect(Collectors.toList());
    
        }
    
        @Override
        public boolean isValid(GenderTypeEnum value, ConstraintValidatorContext context) {
    Arrays.asList(subset).contains(value);
            if(value == null){
                return true;
            }
            return acceptedValues.contains(value.toString());
    
        }
    
    }

已更新UserDTO

@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@Builder
public class UserDTO {

    private String name;
    private int age;
    //@GenderTypeEnumSubset(anyOf = {GenderTypeEnum.MALE, GenderTypeEnum.FEMALE})
    //private GenderTypeEnum gender;
    @GenderTypeEnumSubset(enumClass = GenderTypeEnum.class)
    private String gender;
    private List<HobbyEnum> hobby;

}

但还是要说:
[![在此处输入图像描述][5]][5]

这是@Jay Yadav建议的解决方案(谢谢,但还是不行):

<dependency>
   <groupId>org.springframework.boot</groupId>
        <artifactId>
           spring-boot-starter-validation
        </artifactId>
   <version>3.1.0</version>
</dependency>

和/或

package com.example.enumhandler.dto;

import com.example.enumhandler.enumerator.GenderTypeEnum;
import com.example.enumhandler.enumerator.HobbyEnum;
import com.example.enumhandler.validator.GenderTypeEnumSubset;
import lombok.*;

import java.util.List;

@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@Builder
public class UserDTO {

    private String name;
    private int age;
    @GenderTypeEnumSubset(anyOf = {GenderTypeEnum.MALE, GenderTypeEnum.MALE})
    private GenderTypeEnum gender;
    private List<HobbyEnum> hobby;

}

和/或

package com.example.enumhandler.enumerator;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;

import java.util.Arrays;

public enum GenderTypeEnum {

    MALE("male"),
    FEMALE("female");

    private final String value;

    private GenderTypeEnum(String value) {
        this.value = value;
    }

    @JsonValue
    public String getGender(){
        return this.value;
    }

    @JsonCreator
    public static GenderTypeEnum fromValues(String value) {
        for (GenderTypeEnum category : GenderTypeEnum.values()) {
            if (category.getGender().equalsIgnoreCase(value)) {
                return category;
            }
        }
        throw new IllegalArgumentException(
                "Unknown enum type " + value + ", Allowed values are " + Arrays.toString(values()));
    }
}

和/或

package com.example.enumhandler.enumerator;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;

import java.util.Arrays;

public enum HobbyEnum {

    BIKE("bike"),
    CLIMB("climb"),
    TENNIS("tennis");

    private final String value;

    private HobbyEnum(String value) {
        this.value = value;
    }

    @JsonValue
    public String getGender(){
        return this.value;
    }

    @JsonCreator
    public static HobbyEnum fromValues(String value) {
        for (HobbyEnum category : HobbyEnum.values()) {
            if (category.getGender().equalsIgnoreCase(value)) {
                return category;
            }
        }
        throw new IllegalArgumentException(
                "Unknown enum type " + value + ", Allowed values are " + Arrays.toString(values()));
    }

}

和/或

package com.example.enumhandler.validator;

import com.example.enumhandler.enumerator.GenderTypeEnum;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = GenderTypeEnumSubSetValidator.class)
public @interface GenderTypeEnumSubset {
    GenderTypeEnum[] anyOf();
    String message() default "must be any of {anyOf}";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

和/或

package com.example.enumhandler.validator;

import com.example.enumhandler.enumerator.GenderTypeEnum;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;

import java.util.Arrays;

public class GenderTypeEnumSubSetValidator implements ConstraintValidator<GenderTypeEnumSubset, GenderTypeEnum> {
    private GenderTypeEnum[] subset;

    @Override
    public void initialize(GenderTypeEnumSubset constraint) {
        this.subset = constraint.anyOf();
    }

    @Override
    public boolean isValid(GenderTypeEnum value, ConstraintValidatorContext context) {
        return value == null || Arrays.asList(subset).contains(value);
    }
}

和/或

package com.example.enumhandler.controller;

import com.example.enumhandler.dto.UserDTO;
import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("api/v1")
public class UserController {

    @GetMapping(path = "/user")
    @ResponseBody
    public ResponseEntity<?> getUser(

            @RequestBody
            @Valid
            UserDTO user

    ){
        return ResponseEntity
                .ok()
                .body(user);

    }
}

终于
[![在此处输入图像描述][6]][6]
和/或
[![在此处输入图像描述][7]][7]
带错误处理程序的UPEDATE

package com.example.enumhandler.exception;

import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;

import java.time.LocalDateTime;

@AllArgsConstructor
@Data
@Builder
class ApiError {

    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd hh:mm:ss")
    private LocalDateTime timestamp;
    private String status;
    private int code;
    private String message;

}

和/或

package com.example.enumhandler.exception;

import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.time.LocalDateTime;

@RestControllerAdvice
public class UserExceptionHandler {

    @ExceptionHandler(value = {HttpMessageNotReadableException.class})
    @ResponseStatus(value = HttpStatus.BAD_REQUEST)
    public ResponseEntity<?> test(HttpMessageNotReadableException ex, HttpServletRequest httpServlet){
        ApiError error = ApiError
                .builder()
                .timestamp(LocalDateTime.now())
                .status(HttpStatus.BAD_REQUEST.getReasonPhrase())
                .code(HttpStatus.BAD_REQUEST.value())
                .message(ex.getLocalizedMessage())
                .build();
        return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
    }
}

和/或
[![在此输入图像描述][8]][8]

[![在此输入图像描述][9]][9]
但是
[![在此输入图像描述][10]][10]
1.是否可以不显示**“JSON解析错误:无法构造com.example.enumhandler.enumerator.GenderTypeEnum的示例,问题:“我不喜欢显示类的内部代号;?
1.例如,是否可以报告
“允许的值是[male,female]"而不是“允许的值是[MALE,FEMALE]"**(也称为枚举值而不是名称)?
1.是否可以在一个唯一的消息中收集所有ENUM错误?
[4]:https://medium.com/@vandernobrel/creating-custom-bean-validation-annotations-2c21f5e24b06 [5]:https://i.stack.imgur.com/E5t7k.png [6]:https://i.stack.imgur.com/ktHH6.png [7]:https://i.stack.imgur.com/llpxz.png [8]:https://i.stack.imgur.com/AFKu9.png [9]:https://i.stack.imgur.com/cQSgA.png [10]:https://i.stack.imgur.com/D9Uo0.png

k5ifujac

k5ifujac1#

about是否可以报告,例如,“允许的值是[男性,女性]”而不是“允许的值是[男性,女性]”(又名枚举值而不是名称)?

package com.example.enumhandler.enumerator;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;

import java.util.Arrays;

public enum GenderTypeEnum {

    MALE("male"),
    FEMALE("female");

    private final String value;

    private GenderTypeEnum(String value) {
        this.value = value;
    }

    private static String[] getNames(Class<? extends Enum<?>> e) {
        return Arrays.stream(e.getEnumConstants())
                .map(Enum::name)
                .toArray(String[]::new);
    }

    private static String[] getNamesValue(){
        return Arrays
                .stream(values())
                .map(e -> valueOf(e.name()).value)
                .toArray(String[]::new);
    }

    @JsonValue
    public String getGender(){
        return this.value;
    }

    @JsonCreator
    public static GenderTypeEnum fromValues(String value) {
        for (GenderTypeEnum category : GenderTypeEnum.values()) {
            if (category.getGender().equalsIgnoreCase(value)) {
                return category;
            }
        }
        throw new IllegalArgumentException(
                "Unknown enum type "
                        + value
                        + ", Allowed values are "
                        + Arrays.toString(getNamesValue()));
    }
}

lnlaulya

lnlaulya2#

您错过了在控制器上添加@Valid注解以使自定义注解工作!!

@GetMapping(path = "/user")
    @ResponseBody
    public ResponseEntity<?> getUser(
            @RequestBody
            @Valid
            UserDTO user) {
        return ResponseEntity
                .ok()
                .body(user);

    }

还有一个建议是使用spring-boot-starter-validation依赖项,而不是像spring docs中那样使用hibernate-validator!!
另外,如果您想处理由于传递的无效枚举而发生的异常,则需要创建自定义异常处理程序

@RestControllerAdvice
public class UserExceptionHandler {

    @ExceptionHandler(value = { HttpMessageNotReadableException.class})
    public void test(HttpMessageNotReadableException ex, HttpServletRequest httpServlet){
        //Your code to return your Response here
    }
}

Is it possible to not show "JSON parse error: Cannot construct instance of com.example.enumhandler.enumerator.GenderTypeEnum, problem:" I don't like show internal code name class;?
参考以下内容:https://stackoverflow.com/a/64576320/16769477
Is it possible to report, for example, "Allowed values are [male, female]" instead of "Allowed values are [MALE, FEMALE]" (aka enum value and not name)?
在你的枚举类(HobbyEnum.java)中,你在传递非法的值!!

相关问题