簡體   English   中英

SpringBoot:REST endpint 中@RequestParam 參數的自定義驗證

[英]SpringBoot: Custom validation for a @RequestParam parameter in a REST endpint

問題很簡單,但我還沒有找到解決方案:

我懂了:

@RequestMapping("/example")
public class ExampleController {

    @GetMapping("get")
    public List<WhateverObject> getWhateverObjects(@RequestParam String objectName) {
        
        /* Code */
    }

}

我們正在使用 SpringBoot,我希望根據定義的值列表驗證“objectName”(此列表是枚舉類型,但那部分很容易發生變化,所以我不介意是否需要寫下這些值用手)。 我所看到的關於@RequestParam對象驗證的所有內容都只涵蓋基本內容( @Min(value)@NotNull等等。

我知道 Bean 的 CustomValidators,但它不適用於我當前的問題(而且我無法更改參數類型)。 Spring 是否有特定於此自定義驗證的內容,或者我是否需要在/* Code */部分中“直接”進行驗證?

您可以創建自己的ConstraintValidator ,但是您沒有說明是否需要將您的值與Enum的值或其中的內部屬性進行比較。 我將在下一節中包括這兩種情況的示例。


與枚舉值進行比較

正如greenPadawan提到的,您可以通過Enum更改參數類型,如果您可以/只需要它,那是最好的選擇。

以下示例向您解釋了如果您想保留String (甚至更新它以包含更多/其他檢查,如果您願意)如何自定義該用例。 第一步是創建您將用於檢查約束的注釋:

/**
 * The annotated element must be included in value of the given accepted {@link Class} of {@link Enum}.
 */
@Documented
@Retention(RUNTIME)
@Target({FIELD, ANNOTATION_TYPE, PARAMETER})
@Constraint(validatedBy = EnumHasValueValidator.class)
public @interface EnumHasValue {

  String message() default "must be one of the values included in {values}";

  Class<?>[] groups() default {};

  Class<? extends Payload>[] payload() default {};

  /**
   * @return {@link Class} of {@link Enum} used to check the value
   */
  Class<? extends Enum> enumClass();

  /**
   * @return {@code true} if {@code null} is accepted as a valid value, {@code false} otherwise.
   */
  boolean isNullAccepted() default false;

}

第二個是創建你的驗證器本身:

/**
 *    Validates if the given {@link String} matches with one of the values belonging to the
 * provided {@link Class} of {@link Enum}
 */
public class EnumHasValueValidator implements ConstraintValidator<EnumHasValue, String> {

  private static final String ERROR_MESSAGE_PARAMETER = "values";

  List<String> enumValidValues;
  String constraintTemplate;
  private boolean isNullAccepted;

  @Override
  public void initialize(final EnumHasValue hasValue) {
    enumValidValues = Arrays.stream(hasValue.enumClass().getEnumConstants())
                            .map(Enum::name)
                            .collect(Collectors.toList());
    constraintTemplate = hasValue.message();
    isNullAccepted = hasValue.isNullAccepted();
  }


  @Override
  public boolean isValid(String value, ConstraintValidatorContext context) {
    boolean isValid = null == value ? isNullAccepted
                                    : enumValidValues.contains(value);
    if (!isValid) {
        HibernateConstraintValidatorContext hibernateContext = context.unwrap(HibernateConstraintValidatorContext.class);
        hibernateContext.disableDefaultConstraintViolation();
        hibernateContext.addMessageParameter(ERROR_MESSAGE_PARAMETER, enumValidValues)
                        .buildConstraintViolationWithTemplate(constraintTemplate)
                        .addConstraintViolation();
    }
    return isValid;
  }
}

現在您可以在以下示例中使用它:

public enum IngredientEnum {
  CHEESE,
  HAM,
  ONION,
  PINEAPPLE,
  BACON,
  MOZZARELLA
}

和 controller:

@AllArgsConstructor
@RestController
@RequestMapping("/test")
@Validated
public class TestController {

  @GetMapping("/testAgainstEnum")
  public List<WhateverObject> testAgainstEnum(@RequestParam @EnumHasValue(enumClass=IngredientEnum.class) String objectName) {
    ...
  }
}

您可以在下圖中看到一個示例:

EnumHasValue 示例

如您所見,在這種情況下,小寫/大寫都被考慮在內,您可以根據需要在驗證器中更改它


與內部枚舉屬性進行比較

在這種情況下,第一步是定義一種提取此類內部屬性的方法:

/**
 * Used to get the value of an internal property in an {@link Enum}.
 */
public interface IEnumInternalPropertyValue<T> {

  /**
   * Get the value of an internal property included in the {@link Enum}.
   */
  T getInternalPropertyValue();
}


public enum PizzaEnum implements IEnumInternalPropertyValue<String> {
  MARGUERITA("Margherita"),
  CARBONARA("Carbonara");

  private String internalValue;

  PizzaEnum(String internalValue) {
    this.internalValue = internalValue;
  }

  @Override
  public String getInternalPropertyValue() {
    return this.internalValue;
  }
}

所需的注釋和相關的驗證器與前面的非常相似:

/**
 *    The annotated element must be included in an internal {@link String} property of the given accepted
 * {@link Class} of {@link Enum}.
 */
@Documented
@Retention(RUNTIME)
@Target({FIELD, ANNOTATION_TYPE, PARAMETER})
@Constraint(validatedBy = EnumHasInternalStringValueValidator.class)
public @interface EnumHasInternalStringValue {

  String message() default "must be one of the values included in {values}";

  Class<?>[] groups() default {};

  Class<? extends Payload>[] payload() default {};

  /**
   * @return {@link Class} of {@link Enum} used to check the value
   */
  Class<? extends Enum<? extends IEnumInternalPropertyValue<String>>> enumClass();

  /**
   * @return {@code true} if {@code null} is accepted as a valid value, {@code false} otherwise.
   */
  boolean isNullAccepted() default false;
}

驗證器:

/**
 *    Validates if the given {@link String} matches with one of the internal {@link String} property belonging to the
 * provided {@link Class} of {@link Enum}
 */
public class EnumHasInternalStringValueValidator implements ConstraintValidator<EnumHasInternalStringValue, String> {

  private static final String ERROR_MESSAGE_PARAMETER = "values";

  List<String> enumValidValues;
  String constraintTemplate;
  private boolean isNullAccepted;

  @Override
  public void initialize(final EnumHasInternalStringValue hasInternalStringValue) {
    enumValidValues = Arrays.stream(hasInternalStringValue.enumClass().getEnumConstants())
                            .map(e -> ((IEnumInternalPropertyValue<String>)e).getInternalPropertyValue())
                            .collect(Collectors.toList());
    constraintTemplate = hasInternalStringValue.message();
    isNullAccepted = hasInternalStringValue.isNullAccepted();
  }


  @Override
  public boolean isValid(String value, ConstraintValidatorContext context) {
    boolean isValid = null == value ? isNullAccepted
                                    : enumValidValues.contains(value);
    if (!isValid) {
        HibernateConstraintValidatorContext hibernateContext = context.unwrap(HibernateConstraintValidatorContext.class);
        hibernateContext.disableDefaultConstraintViolation();
        hibernateContext.addMessageParameter(ERROR_MESSAGE_PARAMETER, enumValidValues)
                        .buildConstraintViolationWithTemplate(constraintTemplate)
                        .addConstraintViolation();
    }
    return isValid;
  }
}

和 controller:

@AllArgsConstructor
@RestController
@RequestMapping("/test")
@Validated
public class TestController {

  @GetMapping("/testStringInsideEnum")
  public List<WhateverObject> testStringInsideEnum(@RequestParam @EnumHasInternalStringValue(enumClass=PizzaEnum.class) String objectName) {
    ...
  }
}

您可以在下圖中看到一個示例:

EnumHasValue 示例

最后一個注解和驗證器的源碼可以在這里找到

您可以使用您的枚舉作為參數的類型而不是String

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM