Как SpringBoot выполняет проверку параметров, старые птички все так играют!

Spring Boot задняя часть

Всем привет, я Мисти.

Несколько дней назад я написал «SpringBoot, как унифицированный формат возвращается бэкэндом? Вот как это делают старые птицы! «Результаты чтения были хорошими, но слишком много владельцев воспроизводятся, и сегодня мы продолжим. Во-вторых, поговорим о том, как интегрировать параметры в проверку SprinBoot Validator и проверку параметров навыков более высокого порядка (пользовательская проверка, проверка группировки) .

Эта статья опирается на код предыдущей статьи, и в проект добавлена ​​глобальная проверка исключений. (Репозиторий кода находится в конце статьи)

Во-первых, давайте посмотрим, что такое валидатор параметров Validator и зачем нам нужна валидация параметров?

Зачем нужна проверка параметров?

При ежедневной разработке интерфейса, чтобы предотвратить влияние незаконных параметров на бизнес, часто необходимо проверять параметры интерфейса, например, при входе в систему нужно проверять, не пусты ли логин и пароль, а при создании пользователя, вам необходимо подтвердить электронную почту и мобильный телефон.Точен ли формат номера. Проверять параметры интерфейса по одному коду слишком громоздко, а читабельность кода крайне плохая.

Платформа Validator предназначена для решения проблемы, заключающейся в том, что разработчики пишут меньше кода во время разработки, и повышения эффективности разработки; Validator специально используется для проверки параметров интерфейса, таких как общая необходимая проверка, проверка формата электронной почты, а имя пользователя должно находиться между 6 и 12. ждать...

Среда проверки Validator соответствует спецификации проверки JSR-303 (спецификация проверки параметров), JSRJava Specification Requestsаббревиатура от.

Далее давайте посмотрим, как интегрировать инфраструктуру проверки параметров в SpringbBoot.

Встроенная проверка параметров в SpringBoot

Первым шагом является введение зависимостей

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>

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

Примечание: отspringboot-2.3В начале пакет проверки был разделен наstarterкомпоненты, поэтому вам нужно ввести проверку и веб, а такжеspringboot-2.3В предыдущих версиях нужно было только ввести веб-зависимости.

Второй шаг — определить класс сущностей для параметризации.

@Data
public class ValidVO {
    private String id;

    @Length(min = 6,max = 12,message = "appId长度必须位于6到12之间")
    private String appId;

    @NotBlank(message = "名字为必填项")
    private String name;

    @Email(message = "请填写正确的邮箱地址")
    private String email;

    private String sex;

    @NotEmpty(message = "级别不能为空")
    private String level;
}

В реальной разработке необходимо установить соответствующие бизнес-подсказки для полей, которые необходимо проверить, то есть атрибут сообщения.

Общие аннотации ограничений следующие:

аннотация Функция
@AssertFalse Может быть нулевым, должно быть ложным, если не нулевым
@AssertTrue Может быть нулевым, должно быть истинным, если не нулевым
@DecimalMax Параметр не может превышать максимальное значение
@DecimalMin Параметр не может превышать минимальное значение
@Digits Параметр должен быть числом, а количество целых цифр и количество знаков после запятой должны быть в пределах указанного диапазона.
@Future Дата должна быть в будущем от текущей даты
@Past Дата должна быть в прошлом от текущей даты
@Max Максимум не должен превышать это максимальное значение
@Min Максимум не должен быть меньше этого минимума
@NotNull не может быть нулевым, может быть пустым
@Null должен быть нулевым
@Pattern Должен удовлетворять указанному регулярному выражению
@Size Значение size() коллекций, массивов, карт и т. д. должно находиться в пределах указанного диапазона.
@Email Должен быть в формате электронной почты
@Length Длина должна быть в пределах указанного диапазона
@NotBlank Строка не может быть нулевой, и строка не может равняться "" после функции trim().
@NotEmpty Не может быть нулевым, size() наборов, массивов, карт и т. д. не может быть 0; string trim() может быть равен ""
@Range Значение должно быть в пределах указанного диапазона
@URL Должен быть URL

Примечание. Эта таблица представляет собой простое описание функции аннотации и не описывает атрибуты каждой аннотации; подробности см. в исходном коде.

Третий шаг — определить класс проверки для тестирования.

@RestController
@Slf4j
@Validated
public class ValidController {

    @ApiOperation("RequestBody校验")
    @PostMapping("/valid/test1")   
    public String test1(@Validated @RequestBody ValidVO validVO){
        log.info("validEntity is {}", validVO);
        return "test1 valid success";
    }

    @ApiOperation("Form校验")
    @PostMapping(value = "/valid/test2")
    public String test2(@Validated ValidVO validVO){
        log.info("validEntity is {}", validVO);
        return "test2 valid success";
    }
  
  	@ApiOperation("单参数校验")
    @PostMapping(value = "/valid/test3")
    public String test3(@Email String email){
        log.info("email is {}", email);
        return "email valid success";
    }
}

Здесь мы сначала определяем три метода использования test1, test2, test3, test1.@RequestBodyAnnotation используется для приема данных json, отправленных внешним интерфейсом, test2 имитирует отправку формы, а test3 имитирует отправку одного параметра.Обратите внимание, что при использовании проверки с одним параметром вам необходимо добавить аннотацию @Validated к контроллеру, иначе она не вступит в силу..

Четвертый шаг, испытайте эффект

  1. Вызовите метод test1, подсказкаorg.springframework.web.bind.MethodArgumentNotValidExceptionаномальный
POST http://localhost:8080/valid/test1
Content-Type: application/json

{
  "id": 1,
  "level": "12",
  "email": "47693899",
  "appId": "ab1c"
}
{
  "status": 500,
  "message": "Validation failed for argument [0] in public java.lang.String com.jianzh5.blog.valid.ValidController.test1(com.jianzh5.blog.valid.ValidVO) with 3 errors: [Field error in object 'validVO' on field 'email': rejected value [47693899]; codes [Email.validVO.email,Email.email,Email.java.lang.String,Email]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [validVO.email,email]; arguments []; default message [email],[Ljavax.validation.constraints.Pattern$Flag;@26139123,.*]; default message [不是一个合法的电子邮件地址]]...",
  "data": null,
  "timestamp": 1628239624332
}
  1. Вызовите метод test2, подсказкаorg.springframework.validation.BindExceptionаномальный
POST http://localhost:8080/valid/test2
Content-Type: application/x-www-form-urlencoded

id=1&level=12&email=476938977&appId=ab1c
{
  "status": 500,
  "message": "org.springframework.validation.BeanPropertyBindingResult: 3 errors\nField error in object 'validVO' on field 'name': rejected value [null]; codes [NotBlank.validVO.name,NotBlank.name,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [validVO.name,name]; arguments []; default message [name]]; default message [名字为必填项]...",
  "data": null,
  "timestamp": 1628239301951
}
  1. Вызовите метод test3, подсказкаjavax.validation.ConstraintViolationExceptionаномальный
POST http://localhost:8080/valid/test3
Content-Type: application/x-www-form-urlencoded

email=476938977
{
  "status": 500,
  "message": "test3.email: 不是一个合法的电子邮件地址",
  "data": null,
  "timestamp": 1628239281022
}

присоединившисьValidatorПлатформа проверки может помочь нам автоматизировать проверку параметров.

Добавить исключение параметра в глобальный обработчик исключений

Хотя мы определили глобальный перехватчик исключений ранее и увидели, что перехватчик действует, ноValidatorСообщение об ошибке, возвращаемое фреймворком проверки, слишком раздутое и неудобное для чтения. Чтобы упростить подсказку внешнего интерфейса, нам нужно ее упростить.

непосредственно изменять ранее определенныеRestExceptionHandler, который отдельно перехватывает три исключения для проверки параметров:javax.validation.ConstraintViolationException,org.springframework.validation.BindException,org.springframework.web.bind.MethodArgumentNotValidException, код показан ниже:

@ExceptionHandler(value = {BindException.class, ValidationException.class, MethodArgumentNotValidException.class})
public ResponseEntity<ResultData<String>> handleValidatedException(Exception e) {
  ResultData<String> resp = null;

  if (e instanceof MethodArgumentNotValidException) {
    // BeanValidation exception
    MethodArgumentNotValidException ex = (MethodArgumentNotValidException) e;
    resp = ResultData.fail(HttpStatus.BAD_REQUEST.value(),
                           ex.getBindingResult().getAllErrors().stream()
                           .map(ObjectError::getDefaultMessage)
                           .collect(Collectors.joining("; "))
                          );
  } else if (e instanceof ConstraintViolationException) {
    // BeanValidation GET simple param
    ConstraintViolationException ex = (ConstraintViolationException) e;
    resp = ResultData.fail(HttpStatus.BAD_REQUEST.value(),
                           ex.getConstraintViolations().stream()
                           .map(ConstraintViolation::getMessage)
                           .collect(Collectors.joining("; "))
                          );
  } else if (e instanceof BindException) {
    // BeanValidation GET object param
    BindException ex = (BindException) e;
    resp = ResultData.fail(HttpStatus.BAD_REQUEST.value(),
                           ex.getAllErrors().stream()
                           .map(ObjectError::getDefaultMessage)
                           .collect(Collectors.joining("; "))
                          );
  }

  return new ResponseEntity<>(resp,HttpStatus.BAD_REQUEST);
}

Эффект опыта

POST http://localhost:8080/valid/test1
Content-Type: application/json

{
  "id": 1,
  "level": "12",
  "email": "47693899",
  "appId": "ab1c"
}
{
  "status": 400,
  "message": "名字为必填项; 不是一个合法的电子邮件地址; appId长度必须位于6到12之间",
  "data": null,
  "timestamp": 1628435116680
}

Вы чувствуете себя более свежим?

Проверка пользовательских параметров

Хотя аннотаций, предоставляемых Spring Validation, в основном достаточно, перед лицом сложных определений нам все равно нужно определить наши собственные аннотации для достижения автоматической проверки.

Например, атрибут секса в приведенном выше классе сущностей позволяет интерфейсу передавать только два значения перечисления M и F. Как это реализовать?

Первый шаг — создать пользовательскую аннотацию.

@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
@Repeatable(EnumString.List.class)
@Documented
@Constraint(validatedBy = EnumStringValidator.class)//标明由哪个类执行校验逻辑
public @interface EnumString {
    String message() default "value not in enum values.";

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

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

    /**
     * @return date must in this value array
     */
    String[] value();

    /**
     * Defines several {@link EnumString} annotations on the same element.
     *
     * @see EnumString
     */
    @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
    @Retention(RUNTIME)
    @Documented
    @interface List {

        EnumString[] value();
    }
}

Второй шаг — настроить логику проверки

public class EnumStringValidator implements ConstraintValidator<EnumString, String> {
    private List<String> enumStringList;

    @Override
    public void initialize(EnumString constraintAnnotation) {
        enumStringList = Arrays.asList(constraintAnnotation.value());
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if(value == null){
            return true;
        }
        return enumStringList.contains(value);
    }
}

Третий шаг — добавление аннотаций к полям

@ApiModelProperty(value = "性别")
@EnumString(value = {"F","M"}, message="性别只允许为F或M")
private String sex;

Четвертый шаг, испытайте эффект

POST http://localhost:8080/valid/test2
Content-Type: application/x-www-form-urlencoded

id=1&name=javadaily&level=12&email=476938977@qq.com&appId=ab1cdddd&sex=N
{
  "status": 400,
  "message": "性别只允许为F或M",
  "data": null,
  "timestamp": 1628435243723
}

Проверка пакетов

При добавлении объекта ВО некоторые поля обязательны, а при обновлении не обязательны. как указано вышеValidVOСвойства id и appId используются при добавлении операций.Не требуется, так и во время операций редактированияНеобходимый, имя при добавлении операцииНеобходимый, как бы вы справились с этим сценарием?

В реальной разработке я видел, как многие студенты создавали два объекта VO,ValidCreateVO,ValidEditVOЧтобы справиться с такого рода сценой, это действительно может дать эффект, но это вызовет раздувание класса, и ветераны-разработчики очень легко будут смеяться над этим.

image-20210716084136689

фактическиValidatorПлатформа проверки приняла во внимание этот сценарий и предоставила решение, котороеПроверка пакетов, но многие студенты этого не знают. Для использования проверки блоков требуется всего три шага:

Шаг 1: Определите интерфейс пакета

public interface ValidGroup extends Default {
  
    interface Crud extends ValidGroup{
        interface Create extends Crud{

        }

        interface Update extends Crud{

        }

        interface Query extends Crud{

        }

        interface Delete extends Crud{

        }
    }
}

Здесь мы определяем интерфейс группировки ValidGroup, чтобы позволить ему наследоватьjavax.validation.groups.Default, а затем определите несколько различных типов операций в интерфейсе группировки: «Создать», «Обновить», «Запросить», «Удалить». Что касается того, зачем нам нужно наследовать Default, мы поговорим об этом позже.

Второй шаг — назначить группы параметрам в модели.

@Data
@ApiModel(value = "参数校验类")
public class ValidVO {
    @ApiModelProperty("ID")
    @Null(groups = ValidGroup.Crud.Create.class)
    @NotNull(groups = ValidGroup.Crud.Update.class, message = "应用ID不能为空")
    private String id;

    @Null(groups = ValidGroup.Crud.Create.class)
    @NotNull(groups = ValidGroup.Crud.Update.class, message = "应用ID不能为空")
    @ApiModelProperty(value = "应用ID",example = "cloud")
    private String appId;

    @ApiModelProperty(value = "名字")
    @NotBlank(groups = ValidGroup.Crud.Create.class,message = "名字为必填项")
    private String name;
  
  	@ApiModelProperty(value = "邮箱")
    @Email(message = "请填写正取的邮箱地址")
    privte String email;

   	...

}

Укажите группировку для параметра и используйте группировку по умолчанию, если группировка не указана.

Третий шаг — назначить группы методам, требующим проверки параметров.

@RestController
@Api("参数校验")
@Slf4j
@Validated
public class ValidController {

    @ApiOperation("新增")
    @PostMapping(value = "/valid/add")
    public String add(@Validated(value = ValidGroup.Crud.Create.class) ValidVO validVO){
        log.info("validEntity is {}", validVO);
        return "test3 valid success";
    }


    @ApiOperation("更新")
    @PostMapping(value = "/valid/update")
    public String update(@Validated(value = ValidGroup.Crud.Update.class) ValidVO validVO){
        log.info("validEntity is {}", validVO);
        return "test4 valid success";
    }
}

Здесь мы проходимvalueприписыватьadd()а такжеupdate()Методы задают группы Create и Update соответственно.

Четвертый шаг, испытайте эффект

POST http://localhost:8080/valid/add
Content-Type: application/x-www-form-urlencoded

name=javadaily&level=12&email=476938977@qq.com&sex=F

Мы не передавали параметры id и appId в Create, и проверка прошла.

Когда мы вызываем метод обновления с теми же параметрами, он выдает ошибку проверки параметра.

{
  "status": 400,
  "message": "ID不能为空; 应用ID不能为空",
  "data": null,
  "timestamp": 1628492514313
}

Так как электронная почта принадлежит к группе по умолчанию, и наш групповой интерфейсValidGroupунаследовалDefaultгруппировка, поэтому также можно выполнить проверку параметров в поле электронной почты. Такие как:

POST http://localhost:8080/valid/add
Content-Type: application/x-www-form-urlencoded

name=javadaily&level=12&email=476938977&sex=F
{
  "status": 400,
  "message": "请填写正取的邮箱地址",
  "data": null,
  "timestamp": 1628492637305
}

Конечно, если ваша ValidGroup не наследует группу Default, вам нужно добавить ее в атрибут code@Validated(value = {ValidGroup.Crud.Create.class, Default.class}позволитьemailПроверка поля вступает в силу.

резюме

Проверка параметров очень часто используется в фактической разработке, но многие студенты до сих пор остаются только в простом использовании, таком как групповая проверка и проверка пользовательских параметров.Эти два высокоуровневых навыка используются не очень часто.Множественные ВО используются для принятия создания и Сценарии обновлений. Старые птицы легко презирают и высмеивают. Надеюсь, вы справитесь с ними.

Наконец, я Мисти Джем, архитектор, который пишет код, и программист, который работает в области архитектуры. Я с нетерпением жду вашей переадресации и внимания. Конечно, вы также можете добавить мой личный WeChat.jianzh5, давайте поговорим о технологии!

Исходный код старой серии птиц был загружен на GitHub, и вам необходимо ответить на ключевое слово в общедоступной учетной записи [JAVA Daily Knowledge Record]0923 Получать