Проверка данных часто выполняется в обычном процессе кодирования. Может потребоваться реализовать некоторую логику проверки на каждом уровне системы перед выполнением бизнес-обработки. Эти утомительные проверки и наш бизнес-код будут казаться раздутыми вместе. И эти проверки обычно не зависят от бизнеса. Я также использовал Hibernate Validator на работе, но обнаружил, что некоторые люди не использовали его должным образом (даже я вижу некоторые коды проверки if else...), поэтому я решил разобраться с использованием Hibernate Validator здесь.
Bean Validation 2.0 (JSR 380) определяет модель метаданных и API для проверки сущностей и методов. Hibernate Validator в настоящее время является лучшей реализацией. В этой статье в основном рассказывается об использовании Hibernate Validator.
Использование Hibernate Validator
полагаться
Если это проект Spring Boot, тоspring-boot-starter-web
уже зависит отhibernate-validator
охватывать
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
Если это Spring Mvc, его можно добавить напрямуюhibernate-validator
полагаться
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.17.Final</version>
</dependency>
объявление и проверка ограничения bean-компонента, Validator
Сначала добавьте аннотации ограничений к нашим объектам Java.
@Data
@AllArgsConstructor
public class User {
private String id;
@NotBlank
@Size(max = 20)
private String name;
@NotNull
@Pattern(regexp = "[A-Z][a-z][0-9]")
private String password;
@NotNull
private Integer age;
@Max(10)
@Min(1)
private Integer level;
}
Сначала необходимо получить аутентифицированный экземпляр объектаValidator
пример
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
Validator
Интерфейс имеет три метода, которые можно использовать для проверки всего объекта или только одного свойства объекта.
-
Validator#validate()
Проверить все ограничения для всех bean-компонентов -
Validator#validateProperty()
Проверка одного свойства -
Validator#validateValue()
Проверьте, может ли одно свойство данного класса быть успешно проверено
public class UserTest {
private static Validator validator;
@BeforeAll
public static void setUpValidator() {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
}
@Test
public void validatorTest() {
User user = new User(null, "", "!@#$", null, 11);
// 验证所有bean的所有约束
Set<ConstraintViolation<User>> constraintViolations = validator.validate(user);
// 验证单个属性
Set<ConstraintViolation<User>> constraintViolations2 = validator.validateProperty(user, "name");
// 检查给定类的单个属性是否可以成功验证
Set<ConstraintViolation<User>> constraintViolations3 = validator.validateValue(User.class, "password", "sa!");
constraintViolations.forEach(constraintViolation -> System.out.println(constraintViolation.getMessage()));
constraintViolations2.forEach(constraintViolation -> System.out.println(constraintViolation.getMessage()));
constraintViolations3.forEach(constraintViolation -> System.out.println(constraintViolation.getMessage()));
}
}
Результаты теста
不能为空
最大不能超过10
需要匹配正则表达式"[A-Z][a-z][0-9]"
不能为null
不能为空
需要匹配正则表达式"[A-Z][a-z][0-9]"
Объявление и проверка ограничения метода, ExecutableValidator
Начиная с Bean Validation 1.1 ограничения можно применять не только к JavaBeans и их свойствам, но и к параметрам и возвращаемым значениям методов и конструкторов любого типа Java, вот простой пример
public class RentalStation {
public RentalStation(@NotNull String name) {
//...
}
public void rentCar(@NotNull @Future LocalDate startDate, @Min(1) int durationInDays) {
//...
}
@NotNull
@Size(min = 1)
public List<@NotNull String> getCustomers() {
//...
return null;
}
}
ExecutableValidator
Интерфейс может завершить проверку ограничений метода
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
executableValidator = factory.getValidator().forExecutables();
ДолженExecutableValidator
В интерфейсе есть четыре метода:
-
validateParameters()
иvalidateReturnValue()
для проверки метода -
validateConstructorParameters()
иvalidateConstructorReturnValue()
для проверки конструктора
public class RentalStationTest {
private static ExecutableValidator executableValidator;
@BeforeAll
public static void setUpValidator() {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
executableValidator = factory.getValidator().forExecutables();
}
@Test
public void validatorTest() throws NoSuchMethodException {
RentalStation rentalStation = new RentalStation("z");
Method method = RentalStation.class.getMethod("rentCar", LocalDate.class, int.class);
Object[] parameterValues = {LocalDate.now().minusDays(1), 0};
Set<ConstraintViolation<RentalStation>> violations = executableValidator.validateParameters(
rentalStation, method, parameterValues);
violations.forEach(violation -> System.out.println(violation.getMessage()));
}
}
Результаты теста
需要是一个将来的时间
最小不能小于1
Аннотация ограничения
Существует 22 аннотации ограничений для validator-api-2.0, подробности см. в следующей таблице.
Пустая и ненулевая проверка
аннотация | Поддержка типов Java | инструкция |
---|---|---|
@Null | Object | нулевой |
@NotNull | Object | не ноль |
@NotBlank | CharSequence | не нулевой и должен иметь непробельный символ |
@NotEmpty | CharSequence, Коллекция, Карта, Массив | Не нулевой и не пустой (длина/размер>0) |
проверка логического значения
аннотация | Поддержка типов Java | инструкция | Примечание |
---|---|---|---|
@AssertTrue | логическое значение, логическое значение | истинный | действителен для нуля |
@AssertFalse | логическое значение, логическое значение | ложный | действителен для нуля |
проверка даты
аннотация | Поддержка типов Java | инструкция | Примечание |
---|---|---|---|
@Future | Дата, Календарь, Мгновенное, LocalDate, LocalDateTime, LocalTime, MonthDay, OffsetDateTime, OffsetTime, Year, YearMonth, ZonedDateTime, HijrahDate, JapaneseDate, MinguoDate, ThaiBuddhistDate | Убедитесь, что дата более поздняя, чем текущее время. | действителен для нуля |
@FutureOrPresent | Дата, Календарь, Мгновенное, LocalDate, LocalDateTime, LocalTime, MonthDay, OffsetDateTime, OffsetTime, Year, YearMonth, ZonedDateTime, HijrahDate, JapaneseDate, MinguoDate, ThaiBuddhistDate | Подтвердите, что дата является текущим временем или более поздней | действителен для нуля |
@Past | Дата, Календарь, Мгновенное, LocalDate, LocalDateTime, LocalTime, MonthDay, OffsetDateTime, OffsetTime, Year, YearMonth, ZonedDateTime, HijrahDate, JapaneseDate, MinguoDate, ThaiBuddhistDate | Убедитесь, что дата предшествует текущему времени | действителен для нуля |
@PastOrPresent | Дата, Календарь, Мгновенное, LocalDate, LocalDateTime, LocalTime, MonthDay, OffsetDateTime, OffsetTime, Year, YearMonth, ZonedDateTime, HijrahDate, JapaneseDate, MinguoDate, ThaiBuddhistDate | Подтвердите, что дата является текущим временем или раньше | действителен для нуля |
Численная проверка
аннотация | Поддержка типов Java | инструкция | Примечание |
---|---|---|---|
@Max | BigDecimal, BigInteger, byte, short, int, long и классы-оболочки | меньше или равно | действителен для нуля |
@Min | BigDecimal, BigInteger, byte, short, int, long и классы-оболочки | больше или равно | действителен для нуля |
@DecimalMax | BigDecimal, BigInteger, CharSequence, byte, short, int, long и классы-оболочки | меньше или равно | действителен для нуля |
@DecimalMin | BigDecimal, BigInteger, CharSequence, byte, short, int, long и классы-оболочки | больше или равно | действителен для нуля |
@Negative | BigDecimal, BigInteger, byte, short, int, long, float, double и классы-оболочки | отрицательное число | ноль действителен, 0 недействителен |
@NegativeOrZero | BigDecimal, BigInteger, byte, short, int, long, float, double и классы-оболочки | отрицательный или нулевой | действителен для нуля |
@Positive | BigDecimal, BigInteger, byte, short, int, long, float, double и классы-оболочки | Положительное число | ноль действителен, 0 недействителен |
@PositiveOrZero | BigDecimal, BigInteger, byte, short, int, long, float, double и классы-оболочки | положительный или нулевой | действителен для нуля |
@Digits(integer = 3, fraction = 2) | BigDecimal, BigInteger, CharSequence, byte, short, int, long и классы-оболочки | Целые и десятичные разряды | действителен для нуля |
разное
аннотация | Поддержка типов Java | инструкция | Примечание |
---|---|---|---|
@Pattern | CharSequence | соответствует указанному регулярному выражению | действителен для нуля |
CharSequence | адрес электронной почты | Действителен для нуля, по умолчанию обычный'.*'
|
|
@Size | CharSequence, Коллекция, Карта, Массив | Размерный ряд (длина/размер>0) | действителен для нуля |
Ограничения расширения Hibernate-Validator (частичные)
аннотация | Поддержка типов Java | инструкция |
---|---|---|
@Length | String | Диапазон длины строки |
@Range | Числовые типы и строка | указанный диапазон |
@URL | Проверка URL-адреса |
Аннотации пользовательских ограничений
В дополнение к аннотациям ограничений, представленным выше (которые могут быть удовлетворены в большинстве случаев), мы также можем настроить наши собственные аннотации ограничений в соответствии с нашими собственными потребностями.
Определите пользовательские ограничения, есть три шага
- Создание аннотаций ограничений
- внедрить валидатор
- Определите сообщение об ошибке по умолчанию
Затем давайте определим простую аннотацию для прямой проверки номера мобильного телефона.
@Documented
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Constraint(validatedBy = {MobileValidator.class})
@Retention(RUNTIME)
@Repeatable(Mobile.List.class)
public @interface Mobile {
/**
* 错误提示信息,可以写死,也可以填写国际化的key
*/
String message() default "手机号码不正确";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String regexp() default "^1([38][0-9]|4[579]|5[0-3,5-9]|6[6]|7[0135678]|9[89])\\d{8}$";
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
@Documented
@interface List {
Mobile[] value();
}
}
Я не буду здесь говорить о настройке аннотаций, для пользовательских ограничений требуются следующие 3 атрибута.
-
message
Сообщение об ошибке, вы можете написать его до смерти, или вы можете заполнить интернационализированный ключ -
groups
Информация о группировке, позволяющая указать группу валидации, к которой принадлежит это ограничение (ограничения группировки будут рассмотрены ниже) -
payload
Полезная нагрузка, вы можете использовать полезную нагрузку, чтобы отметить некоторые операции, требующие специальной обработки.
@Repeatable
аннотации иList
Определения позволяют многократно повторять аннотацию в одном и том же месте, обычно с разными конфигурациями (например, с разными группами и сообщениями).
@Constraint(validatedBy = {MobileValidator.class})
Эта аннотация является валидатором, который указывает наше пользовательское ограничение, затем давайте посмотрим на метод написания валидатора, который необходимо реализовать.javax.validation.ConstraintValidator
интерфейс
public class MobileValidator implements ConstraintValidator<Mobile, String> {
/**
* 手机验证规则
*/
private Pattern pattern;
@Override
public void initialize(Mobile mobile) {
pattern = Pattern.compile(mobile.regexp());
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null) {
return true;
}
return pattern.matcher(value).matches();
}
}
ConstraintValidator
Интерфейс определяет два параметра типа, которые задаются в реализации. Первый указывает класс аннотации для проверки (например,Mobile
), второй указывает типы элементов, которые может обрабатывать валидатор (например,String
);initialize()
Методы могут получать доступ к значениям атрибутов аннотаций ограничений;isValid()
Метод используется для проверки и возвращает true, чтобы указать, что проверка пройдена.
Спецификация Bean Validation рекомендует рассматривать нулевые значения как допустимые. если
null
не является допустимым значением для элемента, вы должны использовать@NotNull
явный комментарий
На данный момент наши пользовательские ограничения написаны, и мы можем протестировать их на примере.
public class MobileTest {
public void setMobile(@Mobile String mobile){
// to do
}
private static ExecutableValidator executableValidator;
@BeforeAll
public static void setUpValidator() {
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
executableValidator = factory.getValidator().forExecutables();
}
@Test
public void manufacturerIsNull() throws NoSuchMethodException {
MobileTest mobileTest = new MobileTest();
Method method = MobileTest.class.getMethod("setMobile", String.class);
Object[] parameterValues = {"1111111"};
Set<ConstraintViolation<MobileTest>> violations = executableValidator.validateParameters(
mobileTest, method, parameterValues);
violations.forEach(violation -> System.out.println(violation.getMessage()));
}
}
手机号码不正确
ограничения группировки
В приведенном выше пользовательском ограничении естьgroups
Атрибут используется для указания группировки ограничений проверки.Когда мы аннотируем атрибут, если информация о группировке не настроена, по умолчанию будет использоваться группировка по умолчанию.javax.validation.groups.Default
Группы определяются интерфейсами и используются как идентификаторы Здесь создаются два идентификатора.AddGroup
иUpdateGroup
, соответственно определить новые и модифицированные
public interface AddGroup {
}
public interface UpdateGroup {
}
тогда к нашемуUser
Атрибут id объекта используется в качестве идентификатора группы.
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
@Null(groups = AddGroup.class)
@NotBlank(groups = UpdateGroup.class)
private String id;
// ... 省略了其他属性
}
Давайте посмотрим, как использовать
@Test
public void validatorGroupTest() {
User user = new User();
// 检查给定类的单个属性是否可以成功验证
Set<ConstraintViolation<User>> constraintViolations = validator.validateValue(User.class, "id", "", UpdateGroup.class);
Set<ConstraintViolation<User>> constraintViolations2 = validator.validateValue(User.class, "id", "");
Set<ConstraintViolation<User>> constraintViolations3 = validator.validateValue(User.class, "id", "", AddGroup.class);
constraintViolations.forEach(constraintViolation -> System.out.println(constraintViolation.getMessage()));
constraintViolations2.forEach(constraintViolation -> System.out.println(constraintViolation.getMessage()));
constraintViolations3.forEach(constraintViolation -> System.out.println(constraintViolation.getMessage()));
}
Вышеупомянутый тест только добавляетUpdateGroug
Группа будет проверена, и будет возвращено сообщение об ошибке, а следующее ограничениеViolations2 не будет проверено, поскольку будет использоваться значение по умолчанию.Default
группировка. Если вы хотите снять отметку с группы, она также проверитDefault
Группировка, вы можете унаследовать группировку по умолчанию
public interface AddGroup extends Default {
}
Использование Hibernate Validator с Spring
Вышеприведенное представляет некоторые варианты использования Validator, а также введение аннотаций, так как же мы можем использовать Hibernate Validator для проверки в Spring? Или как использовать Hibernate Validator в веб-проектах?
spring-boot-starter-web
добавляется вhibernate-validator
Зависимый, что указывает на то, что сама Spring Boot также использует структуру проверки Hibernate Validator.
Настроить валидаторы
@Configuration
public class ValidatorConfig {
/**
* 配置验证器
*
* @return validator
*/
@Bean
public Validator validator() {
ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
.configure()
// 快速失败模式
.failFast(true)
// .addProperty( "hibernate.validator.fail_fast", "true" )
.buildValidatorFactory();
return validatorFactory.getValidator();
}
}
через методfailFast(true)
илиaddProperty("hibernate.validator.fail_fast", "true")
Установить каксбой в быстром режиме, режим быстрого отказа. В процессе проверки при обнаружении первого параметра, не соответствующего условиям, он немедленно возвращается и не продолжает проверку последующих параметров. В противном случае все параметры будут проверены одновременно, и будут возвращены все сообщения об ошибках, не соответствующие требованиям.
Если это Spring MVC, требуется конфигурация xml, см. следующую конфигурацию.
<mvc:annotation-driven validator="validator"/>
<!-- validator基本配置 -->
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
<property name="providerClass" value="org.hibernate.validator.HibernateValidator"/>
<!-- 映射资源文件 -->
<property name="validationMessageSource" ref="messageSource"/>
</bean>
<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource" name="messageSource">
<!--<property name="basenames">
<list>
<value>classpath:messages/messages</value>
<value>classpath:messages/ValidationMessages</value>
</list>
</property>-->
<property name="useCodeAsDefaultMessage" value="false" />
<property name="defaultEncoding" value="UTF-8" />
<property name="cacheSeconds" value="60" />
</bean>
проверка компонента запроса параметра
Проверка бина на интерфейсе должна быть добавлена перед параметром@Valid
или весна@Validated
аннотации, обе из которых вызывают применение стандартной проверки bean-компонента. Выдает, если проверка не удаласьBindException
исключение и становится ответом 400 (BAD_REQUEST); или может быть переданоErrors
илиBindingResult
Параметры обрабатывают ошибки проверки локально внутри контроллера. Кроме того, если параметру предшествует@RequestBody
аннотация, будут выданы ошибки проверкиMethodArgumentNotValidException
аномальный.
@RestController
public class UserController {
@PostMapping("/user")
public R handle(@Valid @RequestBody User user, BindingResult result) {
// 在控制器内本地处理验证错误
if (result.hasErrors()) {
result.getAllErrors().forEach(s -> System.out.println(s.getDefaultMessage()));
return R.fail(result.getAllErrors().get(0).getDefaultMessage());
}
// ...
return R.success();
}
@PostMapping("/user2")
public R handle2(@Valid User user, BindingResult result) {
// 在控制器内本地处理验证错误
if (result.hasErrors()) {
result.getAllErrors().forEach(s -> System.out.println(s.getDefaultMessage()));
return R.fail(result.getAllErrors().get(0).getDefaultMessage());
}
// ...
return R.success();
}
/**
* 验证不通过抛出 `MethodArgumentNotValidException`
*/
@PostMapping("/user3")
public R handle3(@Valid @RequestBody User user) {
// ...
return R.success();
}
/**
* 验证不通过抛出 `BindException`
*/
@PostMapping("/user4")
public R handle4(@Valid User user) {
// ...
return R.success();
}
}
Сотрудничайте с ВеснойBindingResult
Parameters, мы можем обрабатывать ошибки валидации в контроллере, но обычно мы также конвертируем сообщения об ошибках валидации в свой собственный формат возврата, так что явно нет необходимости делать такую обработку ошибок валидации в каждом методе. Мы можем использовать исключение, когда проверка не может выполнить унифицированную обработку ошибок.
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* hibernate validator 数据绑定验证异常拦截
*
* @param e 绑定验证异常
* @return 错误返回消息
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(BindException.class)
public R validateErrorHandler(BindException e) {
ObjectError error = e.getAllErrors().get(0);
log.info("数据验证异常:{}", error.getDefaultMessage());
return R.fail(error.getDefaultMessage());
}
/**
* hibernate validator 数据绑定验证异常拦截
*
* @param e 绑定验证异常
* @return 错误返回消息
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MethodArgumentNotValidException.class)
public R validateErrorHandler(MethodArgumentNotValidException e) {
ObjectError error = e.getBindingResult().getAllErrors().get(0);
log.info("数据验证异常:{}", error.getDefaultMessage());
return R.fail(error.getDefaultMessage());
}
}
Проверка параметров метода
настроить
Hibernate Validator может проверять параметры на уровне метода, что, конечно же, реализовано в Spring.
Мы в конфигурации Валидатора добавляемMethodValidationPostProcessor
Bean, добавьте конфигурацию в указанный выше ValidatorConfig.java.
/**
* 设置方法参数验证器
*/
@Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
MethodValidationPostProcessor postProcessor = new MethodValidationPostProcessor();
// 设置validator模式为快速失败返回
postProcessor.setValidator(validator());
return postProcessor;
}
Если это Spring Mvc, то информация о bean-компоненте должна быть объявлена в spring-mvc.xml, иначе она будет недействительна в контроллере.
<!-- 设置方法参数验证器 -->
<bean id="methodValidationPostProcessor" class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor">
<property name="validator" ref="validator"/>
</bean>
использовать
настроено вышеMethodValidationPostProcessor
, мы можем использовать аннотации ограничений для параметров метода или возвращаемых значений.Следует отметить, что класс, который использует проверку параметров, должен быть добавлен@Validated
Аннотация, в противном случае недействительная
/**
* 一定要加上 `@Validated` 注解
*/
@Validated
@RestController
public class UserController {
@GetMapping("/user")
public R handle(@Mobile String mobile) {
// ...
return R.success();
}
}
Если проверка не пройдена, он выдастConstraintViolationException
Исключения, таким же образом мы можем обрабатывать ошибки проверки в глобальном обработчике исключений и добавлять код в GlobalExceptionHandler.
/**
* spring validator 方法参数验证异常拦截
*
* @param e 绑定验证异常
* @return 错误返回消息
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(ConstraintViolationException.class)
public R defaultErrorHandler(ConstraintViolationException e) {
Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
ConstraintViolation<?> violation = violations.iterator().next();
log.info("数据验证异常:{}", violation.getMessage());
return R.fail(violation.getMessage());
}
группировка
Весна@Validate
Аннотации могут поддерживать групповую проверку
@PostMapping("/user")
public R handle(@Validated(AddGroup.class) @RequestBody User user) {
// ...
return R.success();
}