Framework/Spring Boot

[Spring Boot] @ExceptionHandler, @ControllerAdvice (Exception ๊ณตํ†ต ์ฒ˜๋ฆฌ)

๊ฝ์น˜_๋กœ๊ทธ 2024. 3. 9. 13:51

๐Ÿš€ Exception ๊ณตํ†ต ์ฒ˜๋ฆฌ

๋ณดํ†ต ์˜ˆ์™ธ๋Š” 3๊ฐ€์ง€ ๋ฐฉ๋ฒ•์œผ๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.

  • ์˜ˆ์™ธ ๋ณต๊ตฌ : ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ์˜ˆ์™ธ ์ƒํ™ฉ์— ์•Œ๋งž๊ฒŒ ์ฒ˜๋ฆฌํ•˜์—ฌ ๋ณต๊ตฌํ•œ๋‹ค. (try, catch)
  • ์˜ˆ์™ธ ํšŒํ”ผ : ์˜ˆ์™ธ๋ฅผ ์ง์ ‘ ์ฒ˜๋ฆฌํ•˜์ง€ ์•Š๊ณ  ์˜ˆ์™ธ๋ฅผ ์ƒ์œ„ ๋ฉ”์†Œ๋“œ์— ์œ„์ž„ํ•œ๋‹ค. (throw)
  • ์˜ˆ์™ธ ์ „ํ™˜ : ์˜ˆ์™ธ๋ฅผ ์œ„์ž„ํ•˜๋˜ ๋ฐœ์ƒํ•œ ์˜ˆ์™ธ๋ฅผ ๊ทธ๋Œ€๋กœ ์œ„์ž„ํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹Œ ์ ์ ˆํ•œ ์˜ˆ์™ธ๋กœ ์ „ํ™˜ํ•˜์—ฌ ์œ„์ž„ํ•œ๋‹ค. (restTemplate.doExecute)

์ด๋•Œ ์˜ˆ์™ธ ๋ณต๊ตฌ์— ๋Œ€ํ•œ ๋ฒ”์œ„๋Š” ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

  • ๋ฉ”์†Œ๋“œ ์˜์—ญ : ๋ฉ”์†Œ๋“œ ์˜์—ญ์€ ์ข…์†๋œ ๋ณต๊ตฌ ๊ธฐ๋Šฅ์œผ๋กœ ๋‹จ์ˆœํžˆ try, catch ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.
  • ํด๋ž˜์Šค ์˜์—ญ : ํด๋ž˜์Šค ๋‚ด ๊ณตํ†ต ์˜ˆ์™ธ ๋ณต๊ตฌ๋Š” @ExceptionHandler๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.
  • ์ „์—ญ ์˜์—ญ : ์—ฌ๋Ÿฌ ํด๋ž˜์Šค์˜ ๊ณตํ†ต ์˜ˆ์™ธ ๋ณต๊ตฌ๋Š” @ControllerAdvice๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.

์ฆ‰, '@ ExceptionHandler'์„ ์‚ฌ์šฉํ•˜๋ฉด ํ•˜๋‚˜์˜ ํด๋ž˜์Šค ๋‚ด์— ์žˆ๋Š” Exception์— ๋Œ€ํ•œ ์ฒ˜๋ฆฌ๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค.

ํ•˜์ง€๋งŒ ์œ„์™€ ๊ฐ™์€ ๋ฐฉ๋ฒ•์€ ๋ชจ๋“  ํด๋ž˜์Šค/ํ•จ์ˆ˜์—์„œ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ์— ๋Œ€ํ•œ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ฝ”๋“œ์˜ ์ค‘๋ณต์ด ๋ฐœ์ƒํ•˜๊ณ  ์˜ˆ์™ธ ์ฒ˜๋ฆฌ์— ๋Œ€ํ•œ ํ†ต์ผ์„ฑ์ด ์—†์„ ์ˆ˜ ์žˆ๋‹ค.

`@ExceptionHandler`, `@ControllerAdvice`๋ฅผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋ฉด Exception์„ ์ „์—ญ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.

(HandlerExceptionResolver๋ฅผ ์‚ฌ์šฉํ•ด๋„ ๊ณตํ†ต ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์ง€๋งŒ ์‚ฌ์šฉ๋ฒ•์ด ์•ฝ๊ฐ„ ๋ณต์žกํ•˜๊ณ  ์‘๋‹ต์— message๋ฅผ ๋‹ด์„ ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์— SpringBoot์—์„  ์ฃผ๋กœ `@ExceptionHandler`, `@ControllerAdvice`๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์ฒ˜๋ฆฌํ•œ๋‹ค.)


๐Ÿš€ @ExceptionHandler

`@ExceptionHandler`๋Š” `@Controller`, `@ControllerAdvice`๊ฐ€ ์ ์šฉ๋œ ํด๋ž˜์Šค์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

`@Controller`๊ฐ€ ์ ์šฉ๋œ Controller ํด๋ž˜์Šค ์•ˆ์—์„œ `@ExceptionHandler`๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํ•ด๋‹น Controller ์•ˆ์—์„œ ๋ฐœ์ƒํ•˜๋Š” Exception๋งŒ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 
@RestController
@RequestMapping("/api")
public class SampleController {
 
    @GetMapping("/example")
    public String exampleEndpoint() {
        throw new RuntimeException("This is a sample exception");
    }
 
    @ExceptionHandler(RuntimeException.class)
    public ErrorResponse handleRuntimeException(RuntimeException ex) {
        return new ErrorResponse("Runtime Error: " + ex.getMessage());
    }
}
 
cs

๋”ฐ๋ผ์„œ Controller ํด๋ž˜์Šค๊ฐ€ ์—ฌ๋Ÿฌ ๊ฐœ๋ผ๋ฉด ๋ชจ๋“  ํด๋ž˜์Šค ์•ˆ์— `@ExceptionHandler`์— ๋Œ€ํ•œ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด์„œ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ฝ”๋“œ๊ฐ€ ์ค‘๋ณต๋˜๊ณ  ์ผ๊ด€์ ์ธ ์—๋Ÿฌ ์ฒ˜๋ฆฌ๊ฐ€ ์–ด๋ ค์›Œ์ง„๋‹ค.

๋ฐ˜๋ฉด์— '@ControllerAdvice'๊ฐ€ ์ ์šฉ๋œ ํด๋ž˜์Šค ์•ˆ์—์„œ ์‚ฌ์šฉํ•˜๋ฉด ๋ชจ๋“  Controller ํด๋ž˜์Šค์— ๋Œ€ํ•ด ์ „์—ญ์ ์œผ๋กœ Exception ์ฒ˜๋ฆฌ๊ฐ€ ๊ฐ€๋Šฅํ•ด์ง„๋‹ค.


๐Ÿš€ @ControllerAdvice

`@ControllerAdvice`๋Š” `@Controller`๊ฐ€ ์‚ฌ์šฉ๋œ ๋ชจ๋“  ํด๋ž˜์Šค์—์„œ ๋ฐœ์ƒํ•˜๋Š” ๋ชจ๋“  Exception์„ ๊ณตํ†ต์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.

(`@RestController`๊ฐ€ ์‚ฌ์šฉ๋œ ํด๋ž˜์Šค์— ๋Œ€ํ•œ Exception๋„ ์ฒ˜๋ฆฌํ•œ๋‹ค.)

์ด๋•Œ `@ControllerAdvice`๋ฅผ ์„ ์–ธํ•œ ํด๋ž˜์Šค ์•ˆ์—์„œ `@ExceptionHandler`๋ฅผ ์ •์˜ํ•˜์—ฌ ์›ํ•˜๋Š” Exception๋ณ„๋กœ ๋ถ„๋ฅ˜ํ•˜์—ฌ Exception ์ฒ˜๋ฆฌ๋ฅผ ์ง„ํ–‰ํ•œ๋‹ค.

'@ControllerAdvice'๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์˜ˆ์ œ๋Š” ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@ControllerAdvice
public class ExceptionAdvisor {
   
    @ExceptionHandler(Exception.class)
    public ResponseEntity<CommonResponseVO> customExceptionHandler(Exception exception) {
      log.error(exception.getMessage(), exception);
      return ResponseUtil.createFailResponse(StatusCodeMessageConstant.BAD_REQUEST, HttpStatus.BAD_REQUEST);
    }
 
    @ExceptionHandler(ApplicationException.class)
    public ResponseEntity<CommonResponseVO> applicationExceptionHandler(ApplicationException applicationException){
      log.error(applicationException.getMessage(), applicationException);
      return ResponseUtil.createFailResponse(applicationException.getStatusCodeMessage());
    }
 
   @ExceptionHandler({
          ConstraintViolationException.class
          , MethodArgumentTypeMismatchException.class
          , HttpMessageNotReadableException.class
   })
    public ResponseEntity<CommonResponseVO> invalidParameterValueExceptionHandler(Exception exception) {
      log.error(exception.getMessage(), exception);
      return ResponseUtil.createFailResponse(StatusCodeMessageConstant.PARAMETER_VALUE_ERROR, HttpStatus.BAD_REQUEST);
   }
 
   @ExceptionHandler({
          MissingServletRequestParameterException.class
          , MethodArgumentNotValidException.class
          , BindException.class
   })
   public ResponseEntity<CommonResponseVO> invalidMandatoryParameterExceptionHandler(Exception exception) {
      log.error(exception.getMessage(), exception);
      return ResponseUtil.createFailResponse(StatusCodeMessageConstant.MANDATORY_PARAMETER_ERROR, HttpStatus.BAD_REQUEST);
   }
}
cs

'@RestControllerAdvice'๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์˜ˆ์ œ๋Š” ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 
// ๋ชจ๋“  ์˜ˆ์™ธ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” RestControllerAdvice ํด๋ž˜์Šค
@RestControllerAdvice
public class ExceptionHandlerController {
 
    // CustomException ๋ฐœ์ƒ ์‹œ ์ฒ˜๋ฆฌ
    @ExceptionHandler(CustomException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ErrorResponse handleCustomException(CustomException ex) {
        return new ErrorResponse("Custom Error: " + ex.getMessage());
    }
 
    // ๋ชจ๋“  ๊ธฐํƒ€ ์˜ˆ์™ธ๋ฅผ ์ฒ˜๋ฆฌ
    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public ErrorResponse handleException(Exception ex) {
        return new ErrorResponse("Internal Server Error");
    }
 
}
 
cs

๐Ÿš€ ResponseStatusException

Spring 5 ๋ฒ„์ „๋ถ€ํ„ฐ๋Š” ResponseStatusException ํด๋ž˜์Šค๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค๊ณ  ํ•œ๋‹ค.

๊ฐ„๋‹จํ•˜๊ฒŒ HTTP ์‘๋‹ต ์ฝ”๋“œ์™€ ๋ฉ”์„ธ์ง€๋ฅผ ๋งŒ๋“ค์–ด์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์žฅ์ ์ด ์žˆ์ง€๋งŒ ๊ณตํ†ต ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•ด ์‚ฌ์šฉํ•˜๊ธฐ์—๋Š” ์ ํ•ฉํ•˜์ง€ ์•Š๋‹ค๊ณ  ํ•œ๋‹ค.

1
2
3
4
5
6
7
8
@GetMapping("/user/{id}")
public ResponseEntity method(@PathVariable Long id){
    try{
        return service.metohd(id);
    }catch(Exception e)
        throw new ResponseStatusException(HttpStatus.NOT_FOUND,"error");
    }
}
cs

๐Ÿš€ ResponseUtility

RESTful API๋Š” ResponseEntity๋ฅผ ๊ฐ–์œผ๋ฉฐ ์—ฌ๋Ÿฌ HTTP ์ƒํƒœ ์ฝ”๋“œ์™€ ๋ฉ”์„ธ์ง€๋ฅผ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด์„œ ResponseUtility๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ๋„ ํ•œ๋‹ค.

์ƒํ™ฉ์— ๋”ฐ๋ผ success์™€ fail์„ ๋‚˜๋ˆ„์–ด์„œ ์‚ฌ์šฉํ•˜๊ธฐ๋„ ํ•œ๋‹ค. fail์€ Exception์„ ๋ฐœ์ƒ์‹œํ‚ค์ง€ ์•Š๊ณ  ๋‚ด๋ถ€์ ์œผ๋กœ ์‘๋‹ต ์•ˆ์— status๋ฅผ ๋งŒ๋“ค์–ด ์„ฑ๊ณต, ์‹คํŒจ ์—ฌ๋ถ€๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

โœ”๏ธ @UtilityClass๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด (1) final ํด๋ž˜์Šค๋กœ ์ƒ์„ฑํ•˜๊ณ  (2) private ์ƒ์„ฑ์ž๋ฅผ ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•˜๊ณ  (3) ๋‚ด๋ถ€ ํ•จ์ˆ˜๋ฅผ ์ •์  ๋ฉ”์„œ๋“œ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@UtilityClass
public class ResponseUtility {
     
    public ResponseEntity<CommonResponseVO> createSuccessResponse() {
        return this.createSuccessResponse(null, HttpStatus.OK);
    }
     
    public ResponseEntity<CommonResponseVO> createSuccessResponse(CommonResponseVO commonResponseVO) {
        return this.createSuccessResponse(commonResponseVO, HttpStatus.OK);
    }
     
    public ResponseEntity<CommonResponseVO> createSuccessResponse(HttpStatus httpStatus) {
        return this.createSuccessResponse(null, httpStatus);
    }
 
    public ResponseEntity<CommonResponseVO> createSuccessResponse(CommonResponseVO commonResponseVO, HttpStatus httpStatus) {
        return ResponseEntity.status(httpStatus).body(commonResponseVO);
    }
}
cs

๐Ÿš€ ์ฐธ๊ณ . @RestController vs @Controller / @RestControllerAdvice VS @ControllerAdvice

@Controller์™€ @RestController, @ControllerAdvice์™€ @RestControllerAdvice ๋น„์Šทํ•œ 2๊ฐœ์˜ ์–ด๋…ธํ…Œ์ด์…˜์€ ๋น„์Šทํ•œ ๊ธฐ๋Šฅ์„ ์ˆ˜ํ–‰ํ•˜์ง€๋งŒ ์•ฝ๊ฐ„์˜ ์ฐจ์ด์ ์ด ์กด์žฌํ•œ๋‹ค.

๊ฐ ์–ด๋…ธํ…Œ์ด์…˜์˜ ์ฝ”๋“œ๋ฅผ ๋”ฐ๋ผ๊ฐ€๋ณด๋ฉด `@RestController`๋Š” `@Controller` + `@ResponseBody`์ด๊ณ  `@RestControllerAdvice`๋Š” `@ControllerAdvice` + `@ResponseBody`์ธ ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

Controller์™€ ControllerAdvice ๋‚ด๋ถ€๋ฅผ ๋ณด๋ฉด ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

1
2
3
4
5
6
7
8
9
10
11
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller { ... }
 
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface ControllerAdvice { ... }
cs

RestController์™€ RestControllerAdvice ๋‚ด๋ถ€๋ฅผ ๋ณด๋ฉด ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController { ... }
 
 
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ControllerAdvice
@ResponseBody
public @interface RestControllerAdvice { ... }
cs

์ฆ‰, @Rest~๋กœ ์‹œ์ž‘ํ•˜๋Š” ์–ด๋…ธํ…Œ์ด์…˜์—๋Š” `@ResponseBody`๋ฅผ ํฌํ•จ๋˜์–ด ์žˆ๊ณ  ์ด๋Š” ์ฃผ๋กœ RESTful API์˜ ์‘๋‹ต ๋ฐ์ดํ„ฐ๋ฅผ ๋งŒ๋“œ๋Š”๋ฐ ์‚ฌ์šฉ๋œ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค.

@Controller

  • ์ฃผ๋กœ ์ „ํ†ต์ ์ธ Spring MVC์˜ Controller๋ฅผ ๋‚˜ํƒ€๋‚ธ๋‹ค.
  • View๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฉฐ, ์ฃผ๋กœ JSP, Thymeleaf, Freemarker ๋“ฑ๊ณผ ๊ฐ™์€ ํ…œํ”Œ๋ฆฟ ์—”์ง„์„ ์ด์šฉํ•˜์—ฌ HTML ํŽ˜์ด์ง€๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.
  • ๋ฐ˜ํ™˜๋˜๋Š” ๋ฐ์ดํ„ฐ๊ฐ€ ์ฃผ๋กœ ๋ชจ๋ธ(Model)์„ ํ†ตํ•ด View์— ์ „๋‹ฌ๋˜์–ด ํ™”๋ฉด์„ ๋ Œ๋”๋งํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋œ๋‹ค.

@RestController

  • ์ฃผ๋กœ RESTful ์›น ์„œ๋น„์Šค๋ฅผ ์ฒ˜๋ฆฌํ•  ๋•Œ ์‚ฌ์šฉ๋œ๋‹ค.
  • `@ResponseBody` ์–ด๋…ธํ…Œ์ด์…˜์„ ํฌํ•จํ•˜๊ณ  ์žˆ๊ณ  ๋ฉ”์„œ๋“œ ์ž์ฒด๊ฐ€ ์‘๋‹ต ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฉฐ, ์ฃผ๋กœ JSON ๋˜๋Š” XML ํ˜•์‹์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

 

ํ•˜์ง€๋งŒ RESTful API์—์„œ ๋ฐ˜๋“œ์‹œ @Rest~๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š” ๊ฒƒ์€ ์•„๋‹ˆ๋‹ค.

์˜ˆ๋ฅผ๋“ค์–ด `@ExceptionHandler`๋Š” ์–ด๋– ํ•œ ์‘๋‹ต ๊ฐ์ฒด๋„ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— `@ControllerAdvice`๋กœ ์˜ˆ์™ธ๋ฅผ ์ˆ˜์ง‘ํ•˜๊ณ  RESTful API์ฒ˜๋Ÿผ ResponseEntity ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค์–ด์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

1
2
3
4
5
6
7
8
9
10
11
@ControllerAdvice
public class ExceptionAdvice {
    @ExceptionHandler({RuntimeException.class, Exception.class})
    protected CommonResponseEntity handleRuntimeException(RuntimeException e) {
        return CommonResponseEntity.builder()
                .status(HttpStatus.INTERNAL_SERVER_ERROR)
                .code("INTERNAL_SERVER_ERROR")
                .message(e.getMessage())
                .build();
    }
}
cs

 

๋ฐ˜์‘ํ˜•