๊ฐœ๋ฐœ/Spring Boot

API ์˜ˆ์™ธ ์ฒ˜๋ฆฌ - HandlerExceptionResolver(2) : ResponseStatusExceptionResolver, DefaultHandlerExceptionResolver, ExceptionHandlerExceptionResolver

daramii 2022. 7. 8. 01:04

 

 

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 ์—๋Š” ๋งˆ์น˜ ์Šคํ”„๋ง์˜ ์ปจํŠธ๋กค๋Ÿฌ์˜ ํŒŒ๋ผ๋ฏธํ„ฐ ์‘๋‹ต์ฒ˜๋Ÿผ ๋‹ค์–‘ํ•œ ํŒŒ๋ผ๋ฏธํ„ฐ์™€ ์‘๋‹ต์„ ์ง€์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

์ž์„ธํ•œ ํŒŒ๋ผ๋ฏธํ„ฐ์™€ ์‘๋‹ต์€ ๋‹ค์Œ ๊ณต์‹ ๋ฉ”๋‰ด์–ผ์„ ์ฐธ๊ณ ํ•˜์ž.

https://docs.spring.io/springframework/docs/current/reference/html/web.html#mvc-ann-exceptionhandler-args 

 
 
 
 
์ด๋ฒˆ ์ฃผ์ œ์˜ ๋งˆ์ง€๋ง‰ ๊ธ€์ธ @ControllerAdvice์— ๋Œ€ํ•ด์„œ ๋‹ค์Œ ๊ธ€์—์„œ ์•Œ์•„๋ณด๊ฒ ๋‹ค :)