#java #spring #spring-boot #validation
#java #spring #весенняя загрузка #проверка
Вопрос:
Вопрос простой, но я не нашел решения для этого:
Я получил это:
@RequestMapping("/example")
public class ExampleController {
@GetMapping("get")
public List<WhateverObject> getWhateverObjects(@RequestParam String objectName) {
/* Code */
}
}
Мы используем SpringBoot, и я хочу проверить «objectName» на соответствие определенному списку значений (этот список имеет тип enum, но эта часть подвержена изменениям, поэтому я не буду возражать, если мне нужно будет записать значения вручную). Все, что я видел относительно проверки @RequestParam
объектов, охватывает только базовые вещи ( @Min(value)
, @NotNull
и все такое.
Я знаю о пользовательских проверителях для компонентов, но это не относится к моей текущей проблеме (и я не могу изменить тип параметра). Есть ли в Spring что-то конкретное для этой пользовательской проверки или мне нужно выполнить проверку «напрямую» в /* Code */
разделе?
Комментарии:
1. Вы можете использовать свое перечисление в качестве типа вашего параметра вместо string
2. О, это довольно круто. Немного затруднено (я ожидаю появления еще некоторых ограничений и необходимости выполнения дополнительных проверок, вот почему я попросил «пользовательскую проверку», как с beans), но пока это будет работать. Если вы добавите это в качестве ответа, я приму его.
3. Вы можете создать свой
custom validator
, реализовавConstraintValidator
Ответ №1:
Вы можете создать свой собственный ConstraintValidator
, однако вы не говорите, нужно ли вам сравнивать ваше значение со значениями Enum
или с внутренним свойством внутри него. Я включу пример обоих случаев в следующие разделы.
Сравните со значениями 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
}
И контроллер:
@AllArgsConstructor
@RestController
@RequestMapping("/test")
@Validated
public class TestController {
@GetMapping("/testAgainstEnum")
public List<WhateverObject> testAgainstEnum(@RequestParam @EnumHasValue(enumClass=IngredientEnum.class) String objectName) {
...
}
}
Вы можете увидеть пример на следующем рисунке:
(Как вы можете видеть, в этом случае учитываются нижний / верхний регистры, вы можете изменить это в валидаторе, если хотите)
Сравните с внутренним свойством enum
В этом случае первым шагом является определение способа извлечения такого внутреннего свойства:
/**
* 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;
}
}
И контроллер:
@AllArgsConstructor
@RestController
@RequestMapping("/test")
@Validated
public class TestController {
@GetMapping("/testStringInsideEnum")
public List<WhateverObject> testStringInsideEnum(@RequestParam @EnumHasInternalStringValue(enumClass=PizzaEnum.class) String objectName) {
...
}
}
Вы можете увидеть пример на следующем рисунке:
Исходный код последней аннотации и средства проверки можно найти здесь
Комментарии:
1. Хорошо написано, baeldung.com/spring-mvc-custom-validator
2. @AkinOkegbile в следующий раз вы можете попробовать сами и включить это в свой собственный ответ.
3. Извините, что так долго отвечали, но мы вроде как отошли от этой проблемы (закончили с перечислением) и у нас не было времени вернуться и подробнее рассмотреть ее. Сначала я попробовал маршрут пользовательского средства проверки (у нас уже есть несколько), но в итоге отказался от него, потому что казалось, что он несовместим с @RequestParam . Имеет какое-то отношение к использованию материала HibernateConstraint?
4. @Neuromante, не уверен, в чем проблема в вашем случае, я имею в виду, как вы можете видеть, оба примера работают для
@RequestParam
. Возможно, ваша аннотация должна включать какой-то отсутствующий целевой объект, который я использую@Target({FIELD, ANNOTATION_TYPE, PARAMETER})
. Поэтому, если вы хотите использовать его в параметре метода, вам нужно включитьPARAMETER
значение в свое собственное.
Ответ №2:
Вы можете использовать свое перечисление в качестве типа вашего параметра вместо String