一个好的服务总是在处理它之前验证它的数据。在本教程中,我们将研究 Bean Validation API 并在 Spring Boot 应用程序中使用它的参考实现。
**Important**: Until Spring Boot version 2.2 the starter spring-boot-starter-web had as dependency the starter **spring-boot-starter-validation**. In Spring Boot 2.3 the starter spring-boot-starter-validation is NOT a dependency of the starter spring-boot-starter-web anymore so you need to add **explicitly**. the **spring-boot-starter-validation**:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
Bean Validation API 提供了许多可用于验证 bean 的注解。
创建验证涉及两个步骤:
在控制器方法上启用验证。
在 bean 上添加验证。
验证数据的最简单方法是在 Controller 方法中包含约束。看这个例子:
@Component
public class CustomerRepository {
List<Customer> customerList = new ArrayList<Customer>();
@PostConstruct
public void init() {
customerList.add(new Customer(1, "frank", "USA"));
customerList.add(new Customer(2, "john", "USA"));
}
public List<Customer> getData() {
return customerList;
}
public Customer save(Customer c) {
customerList.add(c);
return c;
}
}
这是控制器类:
@Validated
@RestController
public class CustomerController {
@Autowired CustomerRepository repository;
@RequestMapping("/list")
public List<Customer> findAll() {
return repository.getData();
}
@RequestMapping("/find/{id}")
public Customer findOne(@PathVariable @Min(1) @Max(3) int id) {
return repository.getData().get(id);
}
}
如您所见,控制器具有 @Min 和 @Max 约束,它将检查我们是否正在为客户搜索有效的 id。
此外,在这种情况下,@Validated annotation 是在类级别上评估的,即使它允许在方法上使用。
如果我们运行上面的例子,会报如下错误:
curl -s http://localhost:8080/find/5 | jq { "timestamp": "2020-04-02T07:39:58.172+0000", "status": 500, "error": "Internal Server Error", "message": "findOne.id: must be less than or equal to 3", "path": "/find/5" }
如果我们想返回一个 HTTP 状态 400(这是有道理的,因为客户端提供了一个无效的参数,这使它成为一个错误的请求),我们可以添加一个 GlobalExceptionHandler 到我们的控制器:
@ControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler(ConstraintViolationException.class)
public void constraintViolationException(HttpServletResponse response) throws IOException {
response.sendError(HttpStatus.BAD_REQUEST.value());
}
}
现在再次执行请求,您将收到“错误请求”错误,表明请求未正确组合:
curl -s http://localhost:8080/find/5 | jq { "timestamp": "2020-04-02T07:40:44.739+0000", "status": 400, "error": "Bad Request", "message": "findOne.id: must be less than or equal to 3", "path": "/find/5" }
验证也可以放在 Bean 数据上。
验证 Bean 数据可能会变成反模式! 但是,您应该考虑,如果我们只在 Bean 层进行验证,我们会接受 Web 和业务层使用无效数据的风险。无效数据可能会导致业务层出现严重错误,因此 Bean 或持久层中的验证可以作为额外的安全网,但不是唯一的验证场所。
在我们的客户 Bean 中放置了以下约束:
public class Customer {
private int id;
@Size(min = 5, message = "Enter at least 5 Characters.") private String name;
@Country private String country;
public Customer(int id, String name, String country) {
super();
this.id = id;
this.name = name;
this.country = country;
}
当我们尝试持久化一个实体时,将验证上述约束:
@PostMapping("/save") public Customer newCustomer(@Valid @RequestBody Customer customer) {
return repository.save(customer);
}
此外,我们将向我们的 GlobalExceptionHandler 添加以下方法,以在输入数据无效的情况下提供有意义的响应:
@Override protected ResponseEntity < Object > handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
Map < String, Object > body = new LinkedHashMap < > ();
body.put("timestamp", new Date());
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);
}
如果我们尝试保存未通过验证的新客户,将返回以下错误:
curl -v -X POST localhost:8080/save -H "Content-type:application/json" -d "{\"name\":\"Carl\", \"country\":\"USA\"}" {"timestamp":"2020-04-02T09:26:04.546+0000","status":400,"errors":["Enter at least 5 Characters."]}
值得注意的是,可以使用以下验证约束:
如果您对可用的默认约束不满意,您可以创建自定义约束并将它们放置在您的代码中。例如,让我们编写一个自定义约束,它将检查客户 Bean 的国家/地区,只允许来自“美国”或“加拿大”的客户。为此,让我们添加实现 ConstraintValidator 的 CountryValidator
public class CountryValidator implements ConstraintValidator<Country, String> {
List<String> authors = Arrays.asList("USA", "Canada");
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
return authors.contains(value);
}
}
要编译它,我们需要 Country 接口:
@Target({FIELD})
@Retention(RUNTIME)
@Constraint(validatedBy = CountryValidator.class)
@Documented
public @interface Country {
String message() default "We only accept customers from USA and Canada";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
有了这个,如果我们尝试使用无效国家/地区保存客户,它将显示以下错误:
curl -v -X POST localhost:8080/save -H "Content-type:application/json" -d "{\"name\":\"Federick\", \"country\":\"Germany\"}" {"timestamp":"2020-04-02T08:01:16.205+0000","status":400,"errors":["We only accept customers from USA and Canada"]}
要了解验证如何在简单的 MVC 应用程序中处理输入数据,让我们以本教程中的示例为例:如何使用 H2 数据库创建 Spring Boot CRUD 应用程序
在这里,可以在前端层添加客户数据,如下所示:
<form action="#" th:action="@{/addcustomer}" th:object="${customer}" method="post">
<div class="row">
<div >
<label for="name">Name</label>
<input type="text" th:field="*{name}" id="name" placeholder="Name">
<span th:if="${#fields.hasErrors('name')}" th:errors="*{name}" ></span>
</div>
<div >
<label for="surname" class="col-form-label">Surname</label>
<input type="text" th:field="*{surname}" id="surname" placeholder="Surname">
<span th:if="${#fields.hasErrors('surname')}" th:errors="*{surname}" ></span>
</div>
<div >
<label for="email" class="col-form-label">Email</label>
<input type="text" th:field="*{email}" id="email" placeholder="Email">
<span th:if="${#fields.hasErrors('email')}" th:errors="*{email}" ></span>
</div>
</div>
<div class="row">
<div >
<input type="submit" value="Add Customer">
</div>
</div>
</form>
Customer Bean 包含以下约束:
@Entity public class Customer {
@Id @GeneratedValue(strategy = GenerationType.AUTO) private long id;
@NotBlank(message = "Name is required") private String name;
@Size(min = 3, message = "Surname requires at least 3 characters.") private String surname;
@Pattern(regexp = ".+@.+..+", message = "Please provide a valid email address") private String email;
public Customer() {}
public Customer(String name, String email) {
this.name = name;
this.email = email;
}
// Getter / Setters here
}
因此,如果您尝试插入无效的客户,则会在输入表单中直接打印以下错误:
本教程的源代码:https://github.com/fmarchioni/masterspringboot/tree/master/rest/demo-validation
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
内容来源于网络,如有侵权,请联系作者删除!