๐Spring Bean Validation๊ณผ API ์์ธ ์ฒ๋ฆฌ
Bean Validation
- ํน์ ํ๋์ ๋ํ ๊ฒ์ฆ ๋ก์ง์ ๋๋ถ๋ถ ๋น ๊ฐ์ธ์ง ์๋์ง, ํน์ ํฌ๊ธฐ๋ฅผ ๋๋์ง ์๋์ง์ ๊ฐ์ด ๋งค์ฐ ์ผ๋ฐ์ ์ธ ๋ก์ง์ด๋ค.
- ์ ๋
ธํ
์ด์
์ ํตํด์ ๊ฒ์ฆ๋ก์ง์ ์์ฑํ ์ ์๋๋ก ํ์คํํ ๊ฒ์ด
Bean Validation์ด๋ค.
์์กด๊ด๊ณ ์ถ๊ฐ
org.springframework.boot:spring-boot-starter-validation
์ฌ์ฉ๋ก
public class Item {
private Long id;
@NotBlank
private String itemName;
@NotNull
@Range(min = 1000, max = 1000000)
private Integer price;
@NotNull
@Max(9999)
private Integer quantity;
}
ValidatorFactory
- ์คํ๋ง์์๋ ์ง์ ์ฌ์ฉํ ์ผ์ด ์์ง๋ง ์ฐธ๊ณ ๋ก ์์๋๋ค.
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Set<ConstraintViolation<Item>> violations = validator.validate(item)
for (ConstraintViolation<Item> violation : violations) {
violation.getMessage(); // ex: violation.message=1000์์ 1000000 ์ฌ์ด์ฌ์ผ ํฉ๋๋ค
}
์คํ๋ง๊ณผ Bean Validator
spring-boot-starter-validation๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๋ฃ์ผ๋ฉด ์คํ๋ง ๋ถํธ๊ฐ ์๋์ผ๋ก Bean Validator๋ฅผ ์ธ์งํ๊ณLocalValidatorFactoryBean์ ๊ธ๋ก๋ฒ Validator๋ก ๋ฑ๋กํ๋ค.@Valid๋@Validated์ ๋ ธํ ์ด์ ์ ํตํด ๊ฒ์ฆํ ์ ์๋ค. ์ ์๋ ์๋ฐ ํ์ค, ํ์๋ ์คํ๋ง์์ ์ ๊ณตํ๋ ๊ธฐ๋ฅ์ด๋ค.
์์
@PostMapping("/add")
public String addItem(@Validated @RequestBody Dto dto, BindingResult bindingResult) {
if (bindingResult.hasError()) {
log.info(bindingResult);
return bindingResult.getAllErrors();
}
}
- ์ฌ์ฉ๋ฒ์ ์ํ ์ธ์์ ์ธ ์์ ์ด๋ค. ์ค์ ๋ก๋ Request๊ฐ Dto๋ก ๋ณํ์์ฒด๊ฐ ์๋์ ํธ๋ค๋ฌ ์์ผ๋ก ๋ค์ด๊ฐ์ง ๋ชปํ๊ณ ์ด๋ฏธ ์คํจํ๊ฒ ๋๋ค.
API ์์ธ ์ฒ๋ฆฌ
- ์์ธ๋ฅผ ๋์ก์๋(throw) ์ ํ๋ฆฌ์ผ์ด์ ๋ด๋ถ์์ ์๋ฌด๋ ๊ทธ๊ฒ์ ์ฒ๋ฆฌํ์ง ์๋๋ค๋ฉด ์ด๋ป๊ฒ ๋ ๊น?
- ์์ธ๋ ์๋ธ๋ฆฟ์ ํธ์ถํ WAS๊น์ง ์ ๋ฌ๋๋ค. ๊ทธ๋ฆฌ๊ณ MVC์ ๊ฒฝ์ฐ ์์ธ ํ์ด์ง, API์ ๊ฒฝ์ฐ 500๋ฒ๋์ ์๋ฒ ์๋ฌ๋ฅผ ๋ฐ์์ํจ๋ค.
- ์์ธ์ ๊ธฐ๋ณธ์ฒ๋ฆฌ๋
response.sendError(statusCode, reason)์ ํตํด WAS์ ์ ๋ฌ๋๊ณ , ๋ค์ ์๋ธ๋ฆฟ์ ํตํด ์๋ฌํ์ด์ง ํน์ ์๋ฌ์ฒ๋ฆฌ ์๋ธ๋ฆฟ์ด ํธ์ถ๋๋ ๊ตฌ์กฐ์ด๋ค. - ๋ํ
์ผํ๊ฒ ์์ธ๋ฅผ ์ฒ๋ฆฌํ๊ธฐ ์ํด ์คํ๋ง์์๋
ExceptionResolver๋ฅผ ์ ๊ณตํ๋ค.
ExceptionResolver
- ์ฐ์ ์์๋
ExceptionHandlerExceptionResolver,ResponseStatusExceptionResolver,DefaultHandlerExceptionResolver์์ด๋ค.
DefaultHandlerExceptionResolver
- ์คํ๋ง ๋ด๋ถ์ ๊ธฐ๋ณธ ์์ธ๋ฅผ ์ฒ๋ฆฌํ๋ค.
ResponseStatusExceptionResolver
- ์์ธ์ ๋ฐ๋ผ HTTP Status ์ฝ๋๋ฅผ ์ง์ ํด์ฃผ๋ ์ญํ ์ ํ๋ค.
@ResponseStatus๊ฐ ๋ฌ๋ ค์๋ ์์ธ,ResponseStatusException์์ธ ๋ฑ์ ์ฒ๋ฆฌํ๋ค.- ๋ด๋ถ ๊ตฌํ์ ์ผ๋ก๋ ์ด๊ฒ๋
sendError()๋ฅผ ํตํด ๋ค์ ์๋ธ๋ฆฟ์ด ํธ์ถ๋๋ ๊ตฌ์กฐ์ด๋ค.
// @ResponseStatus
@ResponseStatus(code = HttpStatus.BAD_REQUEST, reason = "์๋ชป๋ ์์ฒญ ์ค๋ฅ")
public class BadRequestException extends RuntimeException {
}
// ResponseStatusException - ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ฑ ์์ธ๋ฅผ ์ง์ ์์ ํ๊ธฐ ์ด๋ ค์ด ๊ฒฝ์ฐ ์ฌ์ฉ
@GetMapping("/responseExceptionTest")
public String responseStatusExample() {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "error", new IllegalArgumentException());
}
ExceptionHandlerExceptionResolver
- ๊ฐ์ฅ ์ฐ์ ์์๊ฐ ๋๊ณ ๊ฐ์ฅ ๋ง์ด ์ฌ์ฉ๋๋ ๋ฐฉ์์ด๋ค.
- ์ง์ ํ ์์ธ ๋๋ ๊ทธ ์์ธ์ ์์ ํด๋์ค๋ฅผ ๋ชจ๋ ์ก์ ์ ์๋ค.
@RestController
public class ApiExceptionController {
// ์ปจํธ๋กค๋ฌ ๋ด์ ์์ธ์ฒ๋ฆฌ ํด๋์ค ์ ์
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(IllegalArgumentException.class) // ์์ธ ํด๋์ค๋ ์๋ตํ ์ ์๋ค.
public ErrorResult illegalExHandler(IllegalArgumentException e) {
log.error(e);
return new ErrorResult("BAD", e.getMessage());
}
// ๋์ ์ผ๋ก ์ํ์ฝ๋๋ฅผ ๋ณ๊ฒฝํ๊ณ ์ถ์ ๋
@ExceptionHandler(IllegalArgumentException.class) // ์์ธ ํด๋์ค๋ ์๋ตํ ์ ์๋ค.
public ErrorResult illegalExHandler(IllegalArgumentException e) {
ErrorResult errorResult = new ErrorResult("BAD", e.getMessage());
return new ResponseEntity<>(errorResult, HttpStatus.BAD_REQUEST);
}
// API
@GetMapping("/responseExceptionTest")
public String responseStatusExample() {
throw new IllegalArgumentException("์๋ชป๋ ์
๋ ฅ");
}
}
- ์ ์ฝ๋๋ API๋ฅผ ์ํ ์ฝ๋์ ์์ธ ์ฒ๋ฆฌ ์ฝ๋๊ฐ ์์ฌ์์ด ์์ง๋๊ฐ ๋จ์ด์ง๋ค. ์ด๋ฐ ๊ฒฝ์ฐ
@ControllerAdvice,@RestControllerAdvice๋ฑ์ ์ฌ์ฉํด ๋ถ๋ฆฌํ ์ ์๋ค.
// ๋์์ ์ง์ ํ์ง ์์ผ๋ฉด ๋ชจ๋ ์ปจํธ๋กค๋ฌ์ ๊ธ๋ก๋ฒ๋ก ์ ์ฉ
@ControllerAdvice
// ํน์ ์ ๋
ธํ
์ด์
์ ๊ฐ์ง ์ปจํธ๋กค๋ฌ์๋ง ์ ์ฉํ๋ ๊ฒฝ์ฐ
@ControllerAdvice(annotations = RestController.class)
// ํน์ ํจํค์ง ์์ ์ปจํธ๋กค๋ฌ์๋ง ์ ์ฉํ๋ ๊ฒฝ์ฐ
@ControllerAdvice("org.example.controllers")
// ํน์ ์ปจํธ๋กค๋ฌ์๋ง ์ ์ฉํ๋ ๊ฒฝ์ฐ
@ControllerAdvice(assignableTypes = {ControllerInterface.class, AbsController.class})
public class ApiExceptionAdvice {
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(IllegalArgumentException.class) // ์์ธ ํด๋์ค๋ ์๋ตํ ์ ์๋ค.
public ErrorResult illegalExHandler(IllegalArgumentException e) {
log.error(e);
return new ErrorResult("BAD", e.getMessage());
}
// ๋ค๋ฅธ ์์ธ ์ฒ๋ฆฌ ์ฝ๋
// โฆ
}