概述
在本文中,我们将通过添加bean验证和自定义验证器来增强先前的Spring REST Hello World示例 。 使用的技术: 再次检查以前的REST控制器: 2.1如果类路径上有任何JSR-303实现(例如Hibernate Validator),则将自动启用Bean验证。 默认情况下,Spring Boot将自动获取并下载Hibernate Validator。 2.2下面的POST请求将被传递,我们需要在 2.3使用 2.4将 2.5尝试再次向REST端点发送POST请求。 如果bean验证失败,它将触发 2.6上面的错误响应不是友好的,我们可以捕获 2.7再试一次。 做完了 3.1我们还可以将 3.2在类级别应用 3.3默认错误消息是好的,只是错误代码500不适合。 3.4如果 4.1我们将为 4.2测试。 如果自定义验证器失败,它将触发 5.1使用 5.2使用 $ git clone https://github.com/mkyong/spring-boot.git 翻译自: https://mkyong.com/spring-boot/spring-rest-validation-example/ 1.控制器
package com.mkyong;
import com.mkyong.error.BookNotFoundException;
import com.mkyong.error.BookUnSupportedFieldPatchException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@RestController
public class BookController {
@Autowired
private BookRepository repository;
// Find
@GetMapping("/books")
List<Book> findAll() {
return repository.findAll();
}
// Save
@PostMapping("/books")
@ResponseStatus(HttpStatus.CREATED)
Book newBook(@RequestBody Book newBook) {
return repository.save(newBook);
}
// Find
@GetMapping("/books/{id}")
Book findOne(@PathVariable Long id) {
return repository.findById(id)
.orElseThrow(() -> new BookNotFoundException(id));
}
//...
}
2. Bean验证(休眠验证器)
book
对象上实现bean验证,以确保诸如name
, author
和price
类的字段不为空。 @PostMapping("/books")
Book newBook(@RequestBody Book newBook) {
return repository.save(newBook);
}
curl -v -X POST localhost:8080/books -H "Content-type:application/json" -d "{"name":"ABC"}"
javax.validation.constraints.*
注释对bean进行注释。 package com.mkyong;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.math.BigDecimal;
@Entity
public class Book {
@Id
@GeneratedValue
private Long id;
@NotEmpty(message = "Please provide a name")
private String name;
@NotEmpty(message = "Please provide a author")
private String author;
@NotNull(message = "Please provide a price")
@DecimalMin("1.00")
private BigDecimal price;
//...
}
@Valid
添加到@RequestBody
。 完成后,现在启用了bean验证。 import javax.validation.Valid;
@RestController
public class BookController {
@PostMapping("/books")
Book newBook(@Valid @RequestBody Book newBook) {
return repository.save(newBook);
}
//...
}
MethodArgumentNotValidException
。 默认情况下,Spring将发回HTTP状态400 Bad Request ,但没有错误详细信息。 curl -v -X POST localhost:8080/books -H "Content-type:application/json" -d "{"name":"ABC"}"
Note: Unnecessary use of -X or --request, POST is already inferred.
*
Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> POST /books HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.55.1
> Accept: */*
> Content-type:application/json
> Content-Length: 32
>
* upload completely sent off: 32 out of 32 bytes
< HTTP/1.1 400
< Content-Length: 0
< Date: Wed, 20 Feb 2019 13:02:30 GMT
< Connection: close
<
MethodArgumentNotValidException
并重写响应,如下所示: package com.mkyong.error;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@ControllerAdvice
public class CustomGlobalExceptionHandler extends ResponseEntityExceptionHandler {
// error handle for @Valid
@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/books -H "Content-type:application/json" -d "{"name":"ABC"}"
{
"timestamp":"2019-02-20T13:21:27.653+0000",
"status":400,
"errors":[
"Please provide a author",
"Please provide a price"
]
}
3.路径变量验证
javax.validation.constraints.*
批注直接应用于path变量甚至request参数。 @Validated
,并在路径变量上添加javax.validation.constraints.*
注释,如下所示: import org.springframework.validation.annotation.Validated;
import javax.validation.constraints.Min;
@RestController
@Validated // class level
public class BookController {
@GetMapping("/books/{id}")
Book findOne(@PathVariable @Min(1) Long id) { //jsr 303 annotations
return repository.findById(id)
.orElseThrow(() -> new BookNotFoundException(id));
}
//...
}
curl -v localhost:8080/books/0
{
"timestamp":"2019-02-20T13:27:43.638+0000",
"status":500,
"error":"Internal Server Error",
"message":"findOne.id: must be greater than or equal to 1",
"path":"/books/0"
}
@Validated
失败,它将触发ConstraintViolationException
,我们可以像这样重写错误代码: package com.mkyong.error;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolationException;
import java.io.IOException;
@ControllerAdvice
public class CustomGlobalExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler(ConstraintViolationException.class)
public void constraintViolationException(HttpServletResponse response) throws IOException {
response.sendError(HttpStatus.BAD_REQUEST.value());
}
//..
}
curl -v localhost:8080/books/0
{
"timestamp":"2019-02-20T13:35:59.808+0000",
"status":400,
"error":"Bad Request",
"message":"findOne.id: must be greater than or equal to 1",
"path":"/books/0"
}
4.自定义验证器
author
字段创建一个自定义验证器,仅允许4位作者保存到数据库中。 package com.mkyong.error.validator;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Target({FIELD})
@Retention(RUNTIME)
@Constraint(validatedBy = AuthorValidator.class)
@Documented
public @interface Author {
String message() default "Author is not allowed.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
package com.mkyong.error.validator;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.Arrays;
import java.util.List;
public class AuthorValidator implements ConstraintValidator<Author, String> {
List<String> authors = Arrays.asList("Santideva", "Marie Kondo", "Martin Fowler", "mkyong");
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
return authors.contains(value);
}
}
package com.mkyong;
import com.mkyong.error.validator.Author;
import javax.persistence.Entity;
import javax.validation.constraints.NotEmpty;
//...
@Entity
public class Book {
@Author
@NotEmpty(message = "Please provide a author")
private String author;
//...
MethodArgumentNotValidException
curl -v -X POST localhost:8080/books
-H "Content-type:application/json"
-d "{"name":"Spring REST tutorials", "author":"abc","price":"9.99"}"
{
"timestamp":"2019-02-20T13:49:59.971+0000",
"status":400,
"errors":["Author is not allowed."]
}
5. Spring集成测试
MockMvc
测试 package com.mkyong;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import static org.hamcrest.Matchers.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
@ActiveProfiles("test")
public class BookControllerTest {
private static final ObjectMapper om = new ObjectMapper();
@Autowired
private MockMvc mockMvc;
@MockBean
private BookRepository mockRepository;
/*
{
"timestamp":"2019-03-05T09:34:13.280+0000",
"status":400,
"errors":["Author is not allowed.","Please provide a price","Please provide a author"]
}
*/
@Test
public void save_emptyAuthor_emptyPrice_400() throws Exception {
String bookInJson = "{"name":"ABC"}";
mockMvc.perform(post("/books")
.content(bookInJson)
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON))
.andDo(print())
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.timestamp", is(notNullValue())))
.andExpect(jsonPath("$.status", is(400)))
.andExpect(jsonPath("$.errors").isArray())
.andExpect(jsonPath("$.errors", hasSize(3)))
.andExpect(jsonPath("$.errors", hasItem("Author is not allowed.")))
.andExpect(jsonPath("$.errors", hasItem("Please provide a author")))
.andExpect(jsonPath("$.errors", hasItem("Please provide a price")));
verify(mockRepository, times(0)).save(any(Book.class));
}
/*
{
"timestamp":"2019-03-05T09:34:13.207+0000",
"status":400,
"errors":["Author is not allowed."]
}
*/
@Test
public void save_invalidAuthor_400() throws Exception {
String bookInJson = "{"name":" Spring REST tutorials", "author":"abc","price":"9.99"}";
mockMvc.perform(post("/books")
.content(bookInJson)
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON))
.andDo(print())
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.timestamp", is(notNullValue())))
.andExpect(jsonPath("$.status", is(400)))
.andExpect(jsonPath("$.errors").isArray())
.andExpect(jsonPath("$.errors", hasSize(1)))
.andExpect(jsonPath("$.errors", hasItem("Author is not allowed.")));
verify(mockRepository, times(0)).save(any(Book.class));
}
}
TestRestTemplate
测试 package com.mkyong;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.json.JSONException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.skyscreamer.jsonassert.JSONAssert;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.*;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringRunner;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) // for restTemplate
@ActiveProfiles("test")
public class BookControllerRestTemplateTest {
private static final ObjectMapper om = new ObjectMapper();
@Autowired
private TestRestTemplate restTemplate;
@MockBean
private BookRepository mockRepository;
/*
{
"timestamp":"2019-03-05T09:34:13.280+0000",
"status":400,
"errors":["Author is not allowed.","Please provide a price","Please provide a author"]
}
*/
@Test
public void save_emptyAuthor_emptyPrice_400() throws JSONException {
String bookInJson = "{"name":"ABC"}";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> entity = new HttpEntity<>(bookInJson, headers);
// send json with POST
ResponseEntity<String> response = restTemplate.postForEntity("/books", entity, String.class);
//printJSON(response);
String expectedJson = "{"status":400,"errors":["Author is not allowed.","Please provide a price","Please provide a author"]}";
assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
JSONAssert.assertEquals(expectedJson, response.getBody(), false);
verify(mockRepository, times(0)).save(any(Book.class));
}
/*
{
"timestamp":"2019-03-05T09:34:13.207+0000",
"status":400,
"errors":["Author is not allowed."]
}
*/
@Test
public void save_invalidAuthor_400() throws JSONException {
String bookInJson = "{"name":" Spring REST tutorials", "author":"abc","price":"9.99"}";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<String> entity = new HttpEntity<>(bookInJson, headers);
//Try exchange
ResponseEntity<String> response = restTemplate.exchange("/books", HttpMethod.POST, entity, String.class);
String expectedJson = "{"status":400,"errors":["Author is not allowed."]}";
assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode());
JSONAssert.assertEquals(expectedJson, response.getBody(), false);
verify(mockRepository, times(0)).save(any(Book.class));
}
private static void printJSON(Object object) {
String result;
try {
result = om.writerWithDefaultPrettyPrinter().writeValueAsString(object);
System.out.println(result);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
}
下载源代码
$ cd spring-rest-validation
$ mvn spring-boot:运行 参考文献
最后
以上就是潇洒羽毛为你收集整理的Spring REST验证示例的全部内容,希望文章能够帮你解决Spring REST验证示例所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复