๋ฐ์ํ
request ๋ฐ์ดํฐ ๊ฒ์ฆ์ ์ํด Valid, Validation, Spring Boot์์ ์ ๊ณตํ๋ ๊ธฐ๋ฅ์ ์ฌ์ฉํ ๊ฒฝ์ฐ Exception์ด ๋ฐ์ํ๋ค. ํด๋ผ์ด์ธํธ๊ฐ ๋ฐ์ดํฐ๋ฅผ ์์ฒญํ์ผ๋ ์๋ฌด๋ฐ ์๋ด ์์ด ์๋ฌ๊ฐ ๋ฐ์ํ๋ค๋ฉด ์๋ฌ์ ์์ธ์ ์ ์ ์๋ค. Response์ ์๋ฌ์ ์์ธ์ ์ ํด์ง ๊ท๊ฒฉ์ ๋ง๊ฒ ์ ๊ณตํ์ฌ ํด๋ผ์ด์ธํธ ์์ ์ ๋๊ณ , ๋ฐ์ดํฐ๋ฅผ ๊ฒ์ฆํ์ฌ ์ฌ์ฉํ ์ ์๋ค.
RestControllerAdvice
- Spring MVC์์ ์ ์ญ์ ์ผ๋ก ์์ธ ์ฒ๋ฆฌ๋ฅผ ํ๋ ์ ๋ํ ์ด์
- @ControllerAdvice์ ๋ค๋ฅธ ์ ์ @ResponseBody๋ฅผ ํฌํจํ๊ณ ์๊ธฐ ๋๋ฌธ์ JSON ํ์์ response๋ฅผ ๋ฐํํจ
- ๊ธ๋ก๋ฒ ์์ธ์ฒ๋ฆฌ, ์ฌ์ฉ์ ์ ์ ํ์์ JSON ๊ตฌ์กฐ ๋ฐํํ๋๋ฐ ์ฌ์ฉ
- basePackages, basePackageClasses, annotations ์์ฑ์ผ๋ก ํน์ ์ปจํธ๋กค๋ฌ, ํจํค์ง์์ ๋ฐ์ํ๋ ์์ธ๋ง ์ฒ๋ฆฌํ๊ฒ ํ ์ ์๋ค
- @Order ์ ๋ํ ์ด์ ์ ์ฌ์ฉํด์ controller advice๊ฐ์ ์์๋ฅผ ์ ํ ์๋ ์๋ค.
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
}
- gloabl exception handler์ RestControllerAdvice์ถ๊ฐ
@Builder
@Getter
public class ErrorResponse {
private int code;
private String msg;
@Builder
public ErrorResponse(int code, String msg) {
this.code = code;
this.msg = msg;
}
}
- Response๋ก ์ฌ์ฉํ๊ธฐ ์ํ ๊ฐ์ฒด ์ถ๊ฐ
์์ ์ฝ๋
Valid, Validation exception ๊ด๋ จ ์ค๋ช ์
์ด์ ๊ธ์ ์ฐธ๊ณ ํ์ธ์!
@Getter
@ToString
@JsonNaming(value = PropertyNamingStrategies.SnakeCaseStrategy.class)
public class RegistBookRequest {
@Size(min = 1, max = 20, message = "์ฑ
์ ์ด๋ฆ์ 1~20 ์๋ฆฌ๋ก ๋ฑ๋กํด ์ฃผ์ธ์.")
private String bookName;
@NotBlank(message = "์นดํ
๊ณ ๋ฆฌ ๊ฐ์ ํ์ ์
๋๋ค.")
private String category;
@Past(message = "๋ฐํ์ผ์ ๊ณผ๊ฑฐ๋ ์ง๋ง ๊ฐ๋ฅํฉ๋๋ค. ")
private LocalDate issuedDay;
@Max(value = 100, message = "์ต๋๋ก ๋ฑ๋กํ ์ ์๋ ์๋์ 100๊ฐ ์
๋๋ค.")
private int amount;
}
@RestController
@RequestMapping("/api/v1/book")
public class BookApiController {
@PostMapping
public String regist(
@RequestBody @Valid RegistBookRequest request
) {
return request.toString();
}
@GetMapping("/buy")
public String buy(
@RequestParam(name = "book_name") @Size(min = 3, max = 20, message = "์ฑ
์ ์ด๋ฆ์ 3~20 ์๋ฆฌ๋ก ๋ฑ๋กํด ์ฃผ์ธ์.")
String bookName,
@RequestParam(name = "amount") @Max(value = 10, message = "ํ ๋ฒ์ ์ต๋๋ก ๊ตฌ๋งคํ ์ ์๋ ์๋์ 10๊ฐ ์
๋๋ค.")
int amount
) {
return bookName + " " + amount;
}
}
MethodArgumentNotValidException
- @RequestBody ๊ฐ์ฒด์ ๊ฒ์ฆ์ ์คํจํ ๊ฒฝ์ฐ ๋ฐ์ํ๋ ์๋ฌ
// http://localhost:8080/api/v1/book
{
"book_name": "์ฐ๋ฆฌ๊ฐ ๋น์ ์๋๋ก ๊ฐ ์ ์๋ค๋ฉด",
"issued_day": "2023-12-12",
"amount": 10000
}
- ํ์ ๊ฐ์ธ category๋ฅผ ๋๋ฝ์ํค๊ณ ์ต๋ 100๊น์ง ๊ฐ๋ฅํ amount ๊ฐ์ ์ด๊ณผ ์์ผ request๋ก ์ ๋ฌ
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleMethodArgumentNotValid(MethodArgumentNotValidException ex) {
List<FieldError> fieldErrors = ex.getBindingResult().getFieldErrors();
FieldError fieldError = fieldErrors.get(0);
String field = fieldError.getField(); // amount
String errorMessage = fieldError.getDefaultMessage(); // ์ต๋๋ก ๋ฑ๋กํ ์ ์๋ ์๋์ 100๊ฐ ์
๋๋ค.
Object value = fieldError.getRejectedValue(); // 10000
String objectName = fieldError.getObjectName(); //registBookRequest
ErrorResponse response = ErrorResponse.builder()
.code(ResultCode.NOT_VALID_DATA.getCode())
.msg(errorMessage + " " + field + "ํ๋์ ๊ฐ " + value + "๋ฅผ ํ์ธํ์ธ์")
.build();
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(response);
}
- MethodArgumentNotValidException์ fieldErrors๋ฅผ ํตํด validation์ ์คํจํ ๋ชจ๋ ํ๋์ ๋ํ ์ ๋ณด๋ฅผ ๋ฆฌ์คํธ๋ก ์ ๊ณตํ๋ค
- ์ ํจํ์ง ์์ ๋ฐ์ดํฐ ๊ฒ์ฆ์ ๋ํ ๊ฒฐ๊ณผ๋ฅผ fieldErrors์์ ๋ชจ๋ ํ์ธํ ์ ์์
- fieldError๋ ๊ฒ์ฆ์ ์คํจํ ํ๋๋ช (getField()), ์ ๋ ธํ ์ด์ ์์ message๋ก ์ง์ ํ ๋ฌธ์์ด(getDefaultMessage()), ๊ฒ์ฆ์ ์คํจํ ์์ฒญ ๊ฐ(getRejectedValue()), RequstBody๋ก ์ ๋ฌ ๋ฐ์ ๊ฐ์ฒด์ ์ด๋ฆ(getObjectName()) ๋ฑ์ ์ ๊ณตํ๋ค
{
"code": 40001,
"msg": "์นดํ
๊ณ ๋ฆฌ ๊ฐ์ ํ์ ์
๋๋ค. categoryํ๋์ ๊ฐ null๋ฅผ ํ์ธํ์ธ์"
}
- ํ๋ก ํธ์์ ํ์ธํ ์ ์๋๋ก ๋ฉ์ธ์ง๋ฅผ ์ ๊ณตํ ์ ์๋ค.
ConstraintViolationException
// http://localhost:8080/api/v1/book/buy?book_name=te&amount=11
@RestController
@RequestMapping("/api/v1/book")
@Validated
public class BookApiController {
@GetMapping("/buy")
public String buy(
@RequestParam(name = "book_name") @Size(min = 3, max = 20, message = "์ฑ
์ ์ด๋ฆ์ 3~20 ์๋ฆฌ๋ก ๋ฑ๋กํด ์ฃผ์ธ์.")
String bookName,
@RequestParam(name = "amount") @Max(value = 10, message = "ํ ๋ฒ์ ์ต๋๋ก ๊ตฌ๋งคํ ์ ์๋ ์๋์ 10๊ฐ ์
๋๋ค.")
int amount
) {
return bookName + " " + amount;
}
}
@ExceptionHandler(ConstraintViolationException.class)
public String handleConstraintViolationException(ConstraintViolationException ex) {
StringBuilder sb = new StringBuilder();
List<ConstraintViolation> constraintViolations = new ArrayList<>(ex.getConstraintViolations());
for (ConstraintViolation violation : constraintViolations) {
sb.append("ํ๋ ๋ช
: ").append(violation.getPropertyPath().toString()).append('\n')
.append("๋ฉ์ธ์ง : ").append(violation.getMessage()).append('\n')
.append("๊ฐ : ").append(violation.getInvalidValue()).append("\n\n");
}
return sb.toString();
}
ํ๋ ๋ช
: buy.bookName ๋ฉ์ธ์ง : ์ฑ ์ ์ด๋ฆ์ 3~20 ์๋ฆฌ๋ก ๋ฑ๋กํด ์ฃผ์ธ์. ๊ฐ : te ํ๋ ๋ช : buy.amount ๋ฉ์ธ์ง : ํ ๋ฒ์ ์ต๋๋ก ๊ตฌ๋งคํ ์ ์๋ ์๋์ 10๊ฐ ์ ๋๋ค. ๊ฐ : 11 |
- ๊ฒ์ฆ์ ์คํจํ ๋ชจ๋ ํ๋์ ๋ํ ์ ๋ณด๋ฅผ ์ป์ ์ ์์
- method๋ช .field๋ช , defualt message, ๊ฒ์ฆ ์คํจํ ๊ฐ์ ์ ๊ณต
@ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<ErrorResponse> handleConstraintViolationException(ConstraintViolationException ex) {
StringBuilder sb = new StringBuilder();
ConstraintViolation<?> constraintViolations = ex.getConstraintViolations().iterator().next();
String errorMessage = constraintViolations.getMessage();
ErrorResponse response = ErrorResponse.builder()
.code(ResultCode.NOT_VALID_DATA.getCode())
.msg(errorMessage)
.build();
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(response);
}
{
"code": 40001,
"msg": "์ฑ
์ ์ด๋ฆ์ 3~20 ์๋ฆฌ๋ก ๋ฑ๋กํด ์ฃผ์ธ์."
}
|
HandlerMethodValidationException
http://localhost:8080/api/v1/book/buy?book_name=te&amount=11
@RestController
@RequestMapping("/api/v1/book")
//@Validated
public class BookApiController {
@GetMapping("/buy")
public String buy(
@RequestParam(name = "book_name") @Size(min = 3, max = 20, message = "์ฑ
์ ์ด๋ฆ์ 3~20 ์๋ฆฌ๋ก ๋ฑ๋กํด ์ฃผ์ธ์.")
String bookName,
@RequestParam(name = "amount") @Max(value = 10, message = "ํ ๋ฒ์ ์ต๋๋ก ๊ตฌ๋งคํ ์ ์๋ ์๋์ 10๊ฐ ์
๋๋ค.")
int amount
) {
return bookName + " " + amount;
}
}
@ExceptionHandler(HandlerMethodValidationException.class)
public String handleHandlerMethodValidation(HandlerMethodValidationException ex) {
List<ParameterValidationResult> results = ex.getAllValidationResults();
StringBuilder sb = new StringBuilder();
for (ParameterValidationResult result : results) {
String value = String.valueOf(result.getArgument());
String parameterName = result.getMethodParameter().getParameterName();
String message = result.getResolvableErrors().get(0).getDefaultMessage();
sb.append(value).append("\n").append(parameterName).append("\n").append(message).append("\n\n");
}
return sb.toString();
}
te bookName ์ฑ ์ ์ด๋ฆ์ 3~20 ์๋ฆฌ๋ก ๋ฑ๋กํด ์ฃผ์ธ์. 11 amount ํ ๋ฒ์ ์ต๋๋ก ๊ตฌ๋งคํ ์ ์๋ ์๋์ 10๊ฐ ์ ๋๋ค. |
@ExceptionHandler(HandlerMethodValidationException.class)
public ResponseEntity<ErrorResponse> handleHandlerMethodValidation(HandlerMethodValidationException ex) {
ParameterValidationResult results = ex.getAllValidationResults().get(0);
ErrorResponse response = ErrorResponse.builder()
.code(ResultCode.NOT_VALID_DATA.getCode())
.msg(results.getResolvableErrors().get(0).getDefaultMessage())
.build();
return ResponseEntity.status(ex.getStatusCode())
.body(response);
}
{
"code": 40001,
"msg": "์ฑ
์ ์ด๋ฆ์ 3~20 ์๋ฆฌ๋ก ๋ฑ๋กํด ์ฃผ์ธ์."
}
|
HttpMessageNotReadableException
@ExceptionHandler(HttpMessageNotReadableException.class)
public ResponseEntity<ErrorResponse> handleReadableException(
HttpMessageNotReadableException ex,
HttpServletRequest request
) {
String errorMessage = ex.getMessage(); // JSON parse error: Cannot deserialize value of type `int` from String "ํ๊ฐ": not a valid `int` value
String uri = request.getRequestURI(); // /api/v1/book
ErrorResponse response = ErrorResponse.builder()
.code(ResultCode.NOT_VALID_DATA.getCode())
.msg(errorMessage)
.build();
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(response);
}
// http://localhost:8080/api/v1/book
{
"book_name": "te",
"category": "์ฒ ํ",
"issued_day": "2023-12-12",
"amount": "ํ๊ฐ"
}
- amount๋ int ํ์ ์ด์ด์ผ ํ๊ณ , ์ต๋๋ก ๊ฐ์ง ์ ์๋ 100์ด ์ด๊ณผํ์ง ์์๋์ง ๊ฒ์ฆํ๋ ๋์
- request์ int๋ก ๊ธฐ๋ํ๊ณ ์๋ ํ์ ์ ํ์ฑํ ์ ์๋ String ์ผ๋ก ๋ณด๋ด๋ฉด์ ๋ฐ์ํ ๋ฌธ์
- exception์ message์์ ๋ฌธ์ ๊ฐ ๋ฐ์ํ ๋ฐ์ดํฐ๋ฅผ ์๋ดํ๊ณ ์์ผ๋ฏ๋ก ์ด๋ฅผ ์ด์ฉ
{
"code": 40001,
"msg": "JSON parse error: Cannot deserialize value of type `int` from String \"ํ๊ฐ\": not a valid `int` value"
}
|
728x90
๋ฐ์ํ
'BE > ๐ Spring' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[MQTT] Spring Boot ์์ MQTT(mosquitto) ์ฌ์ฉํ๊ธฐ (4) | 2024.11.13 |
---|---|
[Spring Boot] Custom Validation annotation (0) | 2024.11.10 |
[Spring Boot] Validation์ ๋ฐ์ํ ์ ์๋ ์๋ฌ ์์๋ณด๊ธฐ (0) | 2024.11.08 |
[Validation] spring boot validation ์ฌ์ฉ๋ฒ๊ณผ ์ข ๋ฅ (0) | 2024.11.07 |
[Spring Boot] Controller์ RestController ์ฐจ์ด์ ResponseBody (2) | 2024.09.22 |
๋๊ธ