API ์์ธ ์ฒ๋ฆฌ - HandlerExceptionResolver(2) : ResponseStatusExceptionResolver, DefaultHandlerExceptionResolver, ExceptionHandlerExceptionResolver
https://mong-dev.tistory.com/43
API ์์ธ ์ฒ๋ฆฌ - HandlerExceptionResolver(1)
์คํ๋ง ๋ถํธ์ ์์ธ ์ฒ๋ฆฌ ์คํ๋ง ๋ถํธ์ ๊ธฐ๋ณธ ์ค์ ์ ์ค๋ฅ ๋ฐ์์ /error ๋ฅผ ์ค๋ฅ ํ์ด์ง๋ก ์์ฒญํ๋ค. BasicErrorController ๋ ์ด ๊ฒฝ๋ก๋ฅผ ๊ธฐ๋ณธ์ผ๋ก ๋ฐ๋๋ค. ( server.error.path ๋ก ์์ ๊ฐ๋ฅ, ๊ธฐ๋ณธ ๊ฒฝ๋ก / erro.
mong-dev.tistory.com
์คํ๋ง ๋ถํธ๊ฐ ๊ธฐ๋ณธ์ผ๋ก ์ ๊ณตํ๋ ExceptionResolver ๋ ๋ค์๊ณผ ๊ฐ๋ค.
HandlerExceptionResolverComposite ์ ๋ค์ ์์๋ก ๋ฑ๋ก๋์ด ์๋ค.
1. ExceptionHandlerExceptionResolver
@ExceptionHandler ์ ์ฒ๋ฆฌํ๋ค. API ์์ธ ์ฒ๋ฆฌ๋ ๋๋ถ๋ถ ์ด ๊ธฐ๋ฅ์ผ๋ก ํด๊ฒฐํ๋ค.
2. ResponseStatusExceptionResolver
HTTP ์ํ ์ฝ๋๋ฅผ ์ง์ ํด์ค๋ค. ์) @ResponseStatus(value = HttpStatus.NOT_FOUND)
3. DefaultHandlerExceptionResolver ์ฐ์ ์์๊ฐ ๊ฐ์ฅ ๋ฎ๋ค.
์คํ๋ง ๋ด๋ถ ๊ธฐ๋ณธ ์์ธ๋ฅผ ์ฒ๋ฆฌํ๋ค.
ResponseStatusExceptionResolver
ResponseStatusExceptionResolver ๋ ์์ธ์ ๋ฐ๋ผ์ HTTP ์ํ ์ฝ๋๋ฅผ ์ง์ ํด์ฃผ๋ ์ญํ ์ ํ๋ค.
- @ResponseStatus ๊ฐ ๋ฌ๋ ค์๋ ์์ธ
- ResponseStatusException ์์ธ
๋ค์๊ณผ ๊ฐ์ด ์ฝ๋๋ฅผ ์์ฑํด์ ํ ์คํธ๋ฅผ ํด๋ณผ ์ ์๋ค.
@ResponseStatus(code = HttpStatus.BAD_REQUEST, reason = "์๋ชป๋ ์์ฒญ ์ค๋ฅ")
public class BadRequestException extends RuntimeException {
}
@GetMapping("/api/response-status-ex1")
public String responseStatusEx1() {
throw new BadRequestException();
}
์๋๋ 500์๋ฌ๊ฐ ๋ด์ง๋ง, ์ด์ 400์๋ฌ๋ก ๋ฐ๊ปด์์ ๊ฒ์ด๋ค.
{
"status": 400,
"error": "Bad Request",
"exception": "hello.exception.exception.BadRequestException", "message": "์๋ชป๋ ์์ฒญ ์ค๋ฅ",
"path": "/api/response-status-ex1"'
}
reason ์ MessageSource ์์ ์ฐพ๋ ๊ธฐ๋ฅ๋ ์ ๊ณตํ๋ค. reason = "error.bad"
@ResponseStatus(code = HttpStatus.BAD_REQUEST, reason = "error.bad")
public class BadRequestException extends RuntimeException {
}
messages.properties
error.bad=์๋ชป๋ ์์ฒญ ์ค๋ฅ์ ๋๋ค. ๋ฉ์์ง ์ฌ์ฉ
๊ฒฐ๊ณผ
{
"status": 400,
"error": "Bad Request",
"exception": "hello.exception.exception.BadRequestException",
"message": "์๋ชป๋ ์์ฒญ ์ค๋ฅ์
๋๋ค. ๋ฉ์์ง ์ฌ์ฉ",
"path": "/api/response-status-ex1"
}
๋จ์
@ResponseStatus ๋ ๊ฐ๋ฐ์๊ฐ ์ง์ ๋ณ๊ฒฝํ ์ ์๋ ์์ธ์๋ ์ ์ฉํ ์ ์๋ค. (์ ๋ ธํ ์ด์ ์ ์ง์ ๋ฃ์ด์ผ ํ๋๋ฐ, ๋ด๊ฐ ์ฝ๋๋ฅผ ์์ ํ ์ ์๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ์์ธ ์ฝ๋ ๊ฐ์ ๊ณณ์๋ ์ ์ฉํ ์ ์๋ค.) ์ถ๊ฐ๋ก ์ ๋ ธํ ์ด์ ์ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ ์กฐ๊ฑด์ ๋ฐ๋ผ ๋์ ์ผ๋ก ๋ณ๊ฒฝํ๋ ๊ฒ๋ ์ด๋ ต๋ค. ์ด๋๋ ResponseStatusException ์์ธ๋ฅผ ์ฌ์ฉํ๋ฉด ๋๋ค.
@GetMapping("/api/response-status-ex2")
public String responseStatusEx2() {
throw new ResponseStatusException(HttpStatus.NOT_FOUND
, "error.bad"
, new IllegalArgumentException());
}
{
"status": 404,
"error": "Not Found",
"exception": "org.springframework.web.server.ResponseStatusException",
"message": "์๋ชป๋ ์์ฒญ ์ค๋ฅ์
๋๋ค. ๋ฉ์์ง ์ฌ์ฉ",
"path": "/api/response-status-ex2"
}
DefaultHandlerExceptionResolver
DefaultHandlerExceptionResolver ๋ ์คํ๋ง ๋ด๋ถ์์ ๋ฐ์ํ๋ ์คํ๋ง ์์ธ๋ฅผ ํด๊ฒฐํ๋ค. ๋ํ์ ์ผ๋ก ํ๋ผ๋ฏธํฐ ๋ฐ์ธ๋ฉ ์์ ์ ํ์ ์ด ๋ง์ง ์์ผ๋ฉด ๋ด๋ถ์์ TypeMismatchException ์ด ๋ฐ์ํ๋๋ฐ, ์ด ๊ฒฝ์ฐ ์์ธ๊ฐ ๋ฐ์ํ๊ธฐ ๋๋ฌธ์ ๊ทธ๋ฅ ๋๋ฉด ์๋ธ๋ฆฟ ์ปจํ ์ด๋๊น์ง ์ค๋ฅ๊ฐ ์ฌ๋ผ๊ฐ๊ณ , ๊ฒฐ๊ณผ์ ์ผ๋ก 500 ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ค.
๊ทธ๋ฐ๋ฐ ํ๋ผ๋ฏธํฐ ๋ฐ์ธ๋ฉ์ ๋๋ถ๋ถ ํด๋ผ์ด์ธํธ๊ฐ HTTP ์์ฒญ ์ ๋ณด๋ฅผ ์๋ชป ํธ์ถํด์ ๋ฐ์ํ๋ ๋ฌธ์ ์ด๋ค. HTTP ์์๋ ์ด๋ฐ ๊ฒฝ์ฐ HTTP ์ํ ์ฝ๋ 400์ ์ฌ์ฉํ๋๋ก ๋์ด ์๋ค. DefaultHandlerExceptionResolver ๋ ์ด๊ฒ์ 500 ์ค๋ฅ๊ฐ ์๋๋ผ HTTP ์ํ ์ฝ๋ 400 ์ค๋ฅ๋ก๋ณ๊ฒฝํ๋ค. ์คํ๋ง ๋ด๋ถ ์ค๋ฅ๋ฅผ ์ด๋ป๊ฒ ์ฒ๋ฆฌํ ์ง ์ ๋ง์ ๋ด์ฉ์ด ์ ์๋์ด ์๋ค.
DefaultHandlerExceptionResolver.handleTypeMismatch ๋ฅผ ๋ณด๋ฉด ๋ค์๊ณผ ๊ฐ์ ์ฝ๋๋ฅผ ํ์ธํ ์ ์๋ค.
response.sendError(HttpServletResponse.SC_BAD_REQUEST) (400)
๊ฒฐ๊ตญ response.sendError() ๋ฅผ ํตํด์ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ค.
sendError(400) ๋ฅผ ํธ์ถํ๊ธฐ ๋๋ฌธ์ WAS์์ ๋ค์ ์ค๋ฅ ํ์ด์ง( /error )๋ฅผ ๋ด๋ถ ์์ฒญํ๋ค.
.
.
.
์ง๊ธ๊น์ง HTTP ์ํ ์ฝ๋๋ฅผ ๋ณ๊ฒฝํ๊ณ , ์คํ๋ง ๋ด๋ถ ์์ธ์ ์ํ์ฝ๋๋ฅผ ๋ณ๊ฒฝํ๋ ๊ธฐ๋ฅ๋ ์์๋ณด์๋ค.
๊ทธ๋ฐ๋ฐ HandlerExceptionResolver ๋ฅผ ์ง์ ์ฌ์ฉํ๊ธฐ๋ ๋ณต์กํ๋ค. API ์ค๋ฅ ์๋ต์ ๊ฒฝ์ฐ response ์ ์ง์ ๋ฐ์ดํฐ๋ฅผ ๋ฃ์ด์ผ ํด์ ๋งค์ฐ ๋ถํธํ๊ณ ๋ฒ๊ฑฐ๋กญ๋ค. ModelAndView ๋ฅผ ๋ฐํํด์ผ ํ๋ ๊ฒ๋ API์๋ ์ ๋ง์ง ์๋๋ค.
์คํ๋ง์ ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด @ExceptionHandler ๋ผ๋ ๋งค์ฐ ํ์ ์ ์ธ ์์ธ ์ฒ๋ฆฌ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ค.
๊ทธ๊ฒ์ด ExceptionHandlerExceptionResolver ์ด๋ค.
ExceptionHandlerExceptionResolver
์คํ๋ง์ API ์์ธ ์ฒ๋ฆฌ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด @ExceptionHandler ๋ผ๋ ์ ๋ ธํ ์ด์ ์ ์ฌ์ฉํ๋ ๋งค์ฐ ํธ๋ฆฌํ ์์ธ ์ฒ๋ฆฌ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋๋ฐ, ์ด๊ฒ์ด ๋ฐ๋ก ExceptionHandlerExceptionResolver ์ด๋ค. ์คํ๋ง์ ExceptionHandlerExceptionResolver ๋ฅผ ๊ธฐ๋ณธ์ผ๋ก ์ ๊ณตํ๊ณ , ๊ธฐ๋ณธ์ผ๋ก ์ ๊ณตํ๋ ExceptionResolver ์ค์ ์ฐ์ ์์๋ ๊ฐ์ฅ ๋๋ค. ์ค๋ฌด์์ API ์์ธ ์ฒ๋ฆฌ๋ ๋๋ถ๋ถ ์ด ๊ธฐ๋ฅ์ ์ฌ์ฉํ๋ค.
@ExceptionHandler ์ ๋ ธํ ์ด์ ์ ์ ์ธํ๊ณ , ํด๋น ์ปจํธ๋กค๋ฌ์์ ์ฒ๋ฆฌํ๊ณ ์ถ์ ์์ธ๋ฅผ ์ง์ ํด์ฃผ๋ฉด ๋๋ค. ํด๋น ์ปจํธ๋กค๋ฌ์์ ์์ธ๊ฐ ๋ฐ์ํ๋ฉด ์ด ๋ฉ์๋๊ฐ ํธ์ถ๋๋ค. ์ฐธ๊ณ ๋ก ์ง์ ํ ์์ธ ๋๋ ๊ทธ ์์ธ์ ์์ ํด๋์ค๋ ๋ชจ๋ ์ก์ ์ ์๋ค.
@Data
@AllArgsConstructor
public class ErrorResult {
private String code;
private String message;
}
@Slf4j
@RestController
public class ApiExceptionV2Controller {
//์ํ์ฝ๋๋ ๋ณ๊ฒฝํ๊ณ ์ถ์ผ๋ฉด ์ด๊ฑธ ๋ถ์ฌ์ค๋ค.
@ResponseStatus(HttpStatus.BAD_REQUEST)
//ํด๋น ์ปจํธ๋กค๋ฌ๊ฐ ํธ์ถ๋ ๋, ์๋ฌ๊ฐ ๋ฐ์ํ๋ฉด ์ก๋๋ค.
@ExceptionHandler(IllegalArgumentException.class)
public ErrorResult illegalExHandler(IllegalArgumentException e) {
log.error("[exceptionHandler] ex", e);
return new ErrorResult("BAD", e.getMessage());
}
//ํ๋ผ๋ฏธํฐ์ ์ด๋ฆ์ด ๊ฐ์ผ๋ฉด ๊ดํธ์๋ต๊ฐ๋ฅ.
@ExceptionHandler
public ResponseEntity<ErrorResult> userExHandler(UserException e) {
log.error("[exceptionHandler] ex", e);
ErrorResult errorResult = new ErrorResult("USER-EX", e.getMessage());
return new ResponseEntity(errorResult, HttpStatus.BAD_REQUEST);
}
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler
public ErrorResult exExHandler(Exception e) {
log.error("[exceptionHandler] ex", e);
return new ErrorResult("EX", "๋ด๋ถ์ค๋ฅ");
}
@GetMapping("/api2/members/{id}")
public MemberDto getMember(@PathVariable("id") String id) {
if (id.equals("ex")) {
throw new RuntimeException("์๋ชป๋ ์ฌ์ฉ์");
}
if (id.equals("bad")) {
throw new IllegalArgumentException("์๋ชป๋ ์
๋ ฅ ๊ฐ");
}
if (id.equals("user-ex")) {
throw new UserException("์ฌ์ฉ์ ์ค๋ฅ");
}
return new MemberDto(id, "hello " + id);
}
@Data
@AllArgsConstructor
static class MemberDto {
private String memberId;
private String name;
}
}
Controller์์ ์์ธ๊ฐ ๋ฐ์ํ๋ฉด, ExceptionHandlerExceptionResolver ๊ฐ ๋จผ์ Controller์ @ExceptionHandler๊ฐ ์๋ ์ง ์ฐพ๊ณ , ์์ผ๋ฉด ํธ์ถํด์ค๋ค. ๊ทธ๋ฆฌ๊ณ ์ค๋ฅํด๊ฒฐ์ ์๋ํ๊ณ , ์ ์ํ๋ฆ์ผ๋ก ๋ณ๊ฒฝ๋๋ค. (200์ฝ๋๋ก ๋์ค๊ฒ ๋๋ค.) ๊ทธ๋ฐ๋ฐ, ๋ง์ฝ์ ์ํ์ฝ๋๋ ๋ณ๊ฒฝํ๊ณ ์ถ์ผ๋ฉด @ResponseStatus๋ฅผ ๋ถ์ฌ์ค๋ค. ํน์ ๋๋ฒ์งธ ์ฒ๋ผ ReponseEntity๋ฅผ ์ฌ์ฉํด์ค๋ค.
๋ค์๊ณผ ๊ฐ์ด ๋ค์ํ ์์ธ๋ฅผ ํ๋ฒ์ ์ฒ๋ฆฌํ ์ ์๋ค.
@ExceptionHandler({AException.class, BException.class})
public String ex(Exception e) {
log.info("exception e", e);
}
@ExceptionHandler ์๋ ๋ง์น ์คํ๋ง์ ์ปจํธ๋กค๋ฌ์ ํ๋ผ๋ฏธํฐ ์๋ต์ฒ๋ผ ๋ค์ํ ํ๋ผ๋ฏธํฐ์ ์๋ต์ ์ง์ ํ ์ ์๋ค.
์์ธํ ํ๋ผ๋ฏธํฐ์ ์๋ต์ ๋ค์ ๊ณต์ ๋ฉ๋ด์ผ์ ์ฐธ๊ณ ํ์.