Spring Boot
provides built-in validation annotations, which can help you validate input fields simply and quickly, such as checking null or empty fields and forcing execution Length limits, validating patterns using regular expressions, and validating email addresses.
Some of the most commonly used validation annotations include:
@NotNull
: The specified field cannot be null.
@NotEmpty
: The specified list field cannot be empty.
@NotBlank
: Specifies that the string field must not be empty or contain only spaces.
@Min
and @Max
: Specify the minimum and maximum values for numeric fields.
@Pattern
: Specifies the regular expression pattern that the string field must match.
@Email
: Specifies that the string field must be a valid email address.
For specific usage, refer to the following example:
public class User { @NotNull private Long id; @NotBlank @Size(min = 2, max = 50) private String firstName; @NotBlank @Size(min = 2, max = 50) private String lastName; @Email private String email; @NotNull @Min(18) @Max(99) private Integer age; @NotEmpty private List<String> hobbies; @Pattern(regexp = "[A-Z]{2}\d{4}") private String employeeId;
Although Spring Boot's built-in validation annotations are useful, they May not cover all situations. If there are special parameter validation scenarios, you can use Spring's JSR 303 validation framework to create custom validation annotations. Custom annotations can make your validation logic more reusable and maintainable.
Suppose we have an application where users can create posts. Each post should have a title and body text, and the title should be unique across all posts. While Spring Boot provides built-in validation annotations for checking whether a field is empty, it does not provide built-in validation annotations for checking uniqueness. In this case, we can create a custom validation annotation to handle this situation.
First, we create a custom constraint annotation UniqueTitle
:
@Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = UniqueTitleValidator.class) public @interface UniqueTitle { String message() default "Title must be unique"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
Next, we create a PostRepository
interface for the purpose of retrieving from the database Post:
public interface PostRepository extends JpaRepository<Post, Long> { Post findByTitle(String title); }
Then we need to define the validator class UniqueTitleValidator
as follows:
@Component public class UniqueTitleValidator implements ConstraintValidator<UniqueTitle, String> { @Autowired private PostRepository postRepository; @Override public boolean isValid(String title, ConstraintValidatorContext context) { if (title == null) { return true; } return Objects.isNull(postRepository.findByTitle(title)); } }
UniqueTitleValidator
implements ConstraintValidator
Interface, which has two generic types: the first is the custom annotation UniqueTitle
, and the second is the field type being validated (in this case String
). We also autowired the PostRepository
class to retrieve posts from the database. The
isValid()
method checks if title
is null or if it is unique by querying PostRepository
. If title
is null or unique, validation succeeds and true is returned.
With the custom validation annotation and validator class defined, we can now use it to validate the post title in our Spring Boot application:
public class Post { @UniqueTitle private String title; @NotNull private String body; }
We have @UniqueTitle
The annotation applies to the title
variable in the Post
class. When validating this field, this triggers the validation logic defined in the UniqueTitleValidator
class.
In addition to the front-end or client verification, server-side verification of input is crucial. It ensures that any malicious or malformed data is caught before it is processed or stored, which is critical for application security and stability.
Suppose we have a REST
endpoint that allows users to create new accounts. The endpoint expects a JSON request body containing the user's username and password. To ensure the input is valid, we can create a DTO (Data Transfer Object) class and apply validation annotations to its fields:
public class UserDTO { @NotBlank private String username; @NotBlank private String password; }
We use the @NotBlank
annotation to ensure username# The ## and
password fields are not empty or null.
@RestController @RequestMapping("/users") @Validated public class UserController { @Autowired private UserService userService; @PostMapping public ResponseEntity<String> createUser(@Valid @RequestBody UserDTO userDto) { userService.createUser(userDto); return ResponseEntity.status(HttpStatus.CREATED).body("User created successfully"); } }
@Validated annotation To enable method-level validation, we also apply the
@Valid annotation to the
userDto parameter to trigger the validation process.
RESTful API that allows users to create new users. We make sure that the name and email address fields are not empty and are between the ages of 18 and 99. In addition to these fields, we also provide a clear error message or "email" if a user attempts to create an account with a duplicate "username". mail".
public class User { @NotBlank(message = "用户名不能为空") private String name; @NotBlank(message = "Email不能为空") @Email(message = "无效的Emaild地址") private String email; @NotNull(message = "年龄不能为空") @Min(value = 18, message = "年龄必须大于18") @Max(value = 99, message = "年龄必须小于99") private Integer age; }
@Valid annotation:
@RestController @RequestMapping("/users") public class UserController { @Autowired private UserService userService; @PostMapping public ResponseEntity<String> createUser(@Valid @RequestBody User user, BindingResult result) { if (result.hasErrors()) { List<String> errorMessages = result.getAllErrors().stream() .map(DefaultMessageSourceResolvable::getDefaultMessage) .collect(Collectors.toList()); return ResponseEntity.badRequest().body(errorMessages.toString()); } // save the user to the database using UserService userService.saveUser(user); return ResponseEntity.status(HttpStatus.CREATED).body("User created successfully"); } }
@ Valid annotation to trigger validation of the
User object, and use the
BindingResult object to catch any validation errors.
如果你的应用程序支持多种语言,则必须使用国际化 (i18n) 以用户首选语言显示错误消息。
以下是在 Spring Boot 应用程序中使用 i18n 处理错误消息的示例
首先,在资源目录下创建一个包含默认错误消息的 messages.properties
文件
# messages.properties user.name.required=Name is required. user.email.invalid=Invalid email format. user.age.invalid=Age must be a number between 18 and 99.
接下来,为每种支持的语言创建一个 messages_xx.properties
文件,例如,中文的 messages_zh_CN.properties
。
user.name.required=名称不能为空. user.email.invalid=无效的email格式. user.age.invalid=年龄必须在18到99岁之间.
然后,更新您的验证注释以使用本地化的错误消息
public class User { @NotNull(message = "{user.id.required}") private Long id; @NotBlank(message = "{user.name.required}") private String name; @Email(message = "{user.email.invalid}") private String email; @NotNull(message = "{user.age.required}") @Min(value = 18, message = "{user.age.invalid}") @Max(value = 99, message = "{user.age.invalid}") private Integer age; }
最后,在 Spring 配置文件中配置 MessageSource bean
以加载 i18n
消息文件
@Configuration public class AppConfig { @Bean public MessageSource messageSource() { ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource(); messageSource.setBasename("messages"); messageSource.setDefaultEncoding("UTF-8"); return messageSource; } @Bean public LocalValidatorFactoryBean validator() { LocalValidatorFactoryBean validatorFactoryBean = new LocalValidatorFactoryBean(); validatorFactoryBean.setValidationMessageSource(messageSource()); return validatorFactoryBean; } }
现在,当发生验证错误时,错误消息将根据随请求发送的“Accept-Language”标头以用户的首选语言显示。
验证组是 Spring Boot 验证框架的一个强大功能,允许您根据其他输入值或应用程序状态应用条件验证规则。
现在有一个包含三个字段的User
类的情况下:firstName
、lastName
和email
。我们要确保如果 email
字段为空,则 firstName
或 lastName
字段必须非空。否则,所有三个字段都应该正常验证。
为此,我们将定义两个验证组:EmailNotEmpty
和 Default
。EmailNotEmpty
组将包含当 email
字段不为空时的验证规则,而 Default
组将包含所有三个字段的正常验证规则。
创建带有验证组的 User
类
public class User { @NotBlank(groups = Default.class) private String firstName; @NotBlank(groups = Default.class) private String lastName; @Email(groups = EmailNotEmpty.class) private String email; // getters and setters omitted for brevity public interface EmailNotEmpty {} public interface Default {} }
请注意,我们在User
类中定义了两个接口,EmailNotEmpty
和 Default
。这些将作为我们的验证组。
接下来,我们更新Controller
使用这些验证组
@RestController @RequestMapping("/users") @Validated public class UserController { public ResponseEntity<String> createUser( @Validated({org.example.model.ex6.User.EmailNotEmpty.class}) @RequestBody User userWithEmail, @Validated({User.Default.class}) @RequestBody User userWithoutEmail) { // Create the user and return a success response } }
我们已将@Validated
注释添加到我们的控制器,表明我们想要使用验证组。我们还更新了 createUser
方法,将两个 User
对象作为输入,一个在 email
字段不为空时使用,另一个在它为空时使用。
@Validated
注释用于指定将哪个验证组应用于每个 User
对象。对于 userWithEmail
参数,我们指定了 EmailNotEmpty
组,而对于 userWithoutEmail
参数,我们指定了 Default
组。
进行这些更改后,现在将根据“电子邮件”字段是否为空对“用户”类进行不同的验证。如果为空,则 firstName
或 lastName
字段必须非空。否则,所有三个字段都将正常验证。
如果需要验证跨多个字段的复杂输入规则,可以使用跨字段验证来保持验证逻辑的组织性和可维护性。跨字段验证可确保所有输入值均有效且彼此一致,从而防止出现意外行为。
假设我们有一个表单,用户可以在其中输入任务的开始日期和结束日期,并且我们希望确保结束日期不早于开始日期。我们可以使用跨域验证来实现这一点。
首先,我们定义一个自定义验证注解EndDateAfterStartDate
:
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = EndDateAfterStartDateValidator.class) public @interface EndDateAfterStartDate { String message() default "End date must be after start date"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
然后,我们创建验证器EndDateAfterStartDateValidator
:
public class EndDateAfterStartDateValidator implements ConstraintValidator<EndDateAfterStartDate, TaskForm> { @Override public boolean isValid(TaskForm taskForm, ConstraintValidatorContext context) { if (taskForm.getStartDate() == null || taskForm.getEndDate() == null) { return true; } return taskForm.getEndDate().isAfter(taskForm.getStartDate()); } }
最后,我们将EndDateAfterStartDate
注释应用于我们的表单对象TaskForm
:
@EndDateAfterStartDate public class TaskForm { @NotNull @DateTimeFormat(pattern = "yyyy-MM-dd") private LocalDate startDate; @NotNull @DateTimeFormat(pattern = "yyyy-MM-dd") private LocalDate endDate; }
现在,当用户提交表单时,验证框架将自动检查结束日期是否晚于开始日期,如果不是,则提供有意义的错误消息。
可以使用异常处理ExceptionHandler
来统一捕获和处理验证错误。
以下是如何在 Spring Boot 中使用异常处理来处理验证错误的示例:
@RestControllerAdvice public class RestExceptionHandler extends ResponseEntityExceptionHandler { @ExceptionHandler(MethodArgumentNotValidException.class) protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { Map<String, Object> body = new LinkedHashMap<>(); body.put("timestamp", LocalDateTime.now()); body.put("status", status.value()); // Get all errors List<String> errors = ex.getBindingResult() .getFieldErrors() .stream() .map(x -> x.getDefaultMessage()) .collect(Collectors.toList()); body.put("errors", errors); return new ResponseEntity<>(body, headers, status); } }
在这里,我们创建了一个用 @RestControllerAdvice
注解的 RestExceptionHandler
类来处理我们的 REST API 抛出的异常。然后我们创建一个用 @ExceptionHandler
注解的方法来处理在验证失败时抛出的 MethodArgumentNotValidException
。
在处理程序方法中,我们创建了一个 Map
对象来保存错误响应的详细信息,包括时间戳、HTTP 状态代码和错误消息列表。我们使用 MethodArgumentNotValidException
对象的 getBindingResult()
方法获取所有验证错误并将它们添加到错误消息列表中。
最后,我们返回一个包含错误响应详细信息的ResponseEntity
对象,包括作为响应主体的错误消息列表、HTTP 标头和 HTTP 状态代码。
有了这个异常处理代码,我们的 REST API 抛出的任何验证错误都将被捕获并以结构化和有意义的格式返回给用户,从而更容易理解和解决问题。
需要为你的验证逻辑编写单元测试,以帮助确保它正常工作。
@DataJpaTest public class UserValidationTest { @Autowired private TestEntityManager entityManager; @Autowired private Validator validator; @Test public void testValidation() { User user = new User(); user.setFirstName("John"); user.setLastName("Doe"); user.setEmail("invalid email"); Set<ConstraintViolation<User>> violations = validator.validate(user); assertEquals(1, violations.size()); assertEquals("must be a well-formed email address", violations.iterator().next().getMessage()); } }
我们使用 JUnit 5 编写一个测试来验证具有无效电子邮件地址的“用户”对象。然后我们使用 Validator
接口来验证 User
对象并检查是否返回了预期的验证错误。
客户端验证可以通过向用户提供即时反馈并减少对服务器的请求数量来改善用户体验。但是,不应依赖它作为验证输入的唯一方法。客户端验证很容易被绕过或操纵,因此必须在服务器端验证输入,以确保安全性和数据完整性。
The above is the detailed content of What are the techniques for SpringBoot parameter verification?. For more information, please follow other related articles on the PHP Chinese website!