๐ 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 |