Spring ์์ธ์ฒ๋ฆฌ - API, @ExceptionHandler
by JiwonDev2021.08.30 - [Backend/Spring MVC] - Spring ์์ธ์ฒ๋ฆฌ - ์ค๋ฅํ์ด์ง(404,500)
Spring ์์ธ์ฒ๋ฆฌ - ์ค๋ฅํ์ด์ง(404,500)
์๋ธ๋ฆฟ ์ปจํ ์ด๋๋ ์ด๋ค ๋ฐฉ๋ฒ์ผ๋ก ์์ธ๋ฅผ ์ฒ๋ฆฌํ๋์ง ๋ฐฐ์๋ณด๊ณ Spring์์์ ์์ธ์ฒ๋ฆฌ ๋ฐฉ๋ฒ์ ๋ฐฐ์๋ณด์. # ์๋ธ๋ฆฟ ์์ธ ์ฒ๋ฆฌ ์๋ธ๋ฆฟ์ WAS์์ ๋์ํ๋ ์๋ฐ ๊ฐ์ฒด์ด๋ค. ์๋ธ๋ฆฟ์ ๋ค์ 2๊ฐ์ง ๋ฐฉ๋ฒ
jiwondev.tistory.com
๊ณ ๊ฐ์๊ฒ ๋ณด์ฌ์ค HTML ์ค๋ฅํ์ด์ง๋ ์คํ๋ง๋ถํธ์ BasicErrorController๋ฅผ ์ฌ์ฉํ๋ฉด ์ฝ๊ฒ ๊ตฌํ ๊ฐ๋ฅํ๋ค. ๊ทธ๋ ๋ค๋ฉด JSON๋ฑ์ API์ ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ค๋ฉด ์ด๋ป๊ฒ ์ฒ๋ฆฌํ ๊น?
# API์ ์์ธ์ฒ๋ฆฌ
๊ถ๊ธํ๋๊น ์ผ๋จ ์์ธ๋ฅผ ๋์ง๋ฉด ์คํ๋ง์์ ์ด๋ป๊ฒ ๋์ํ๋์ง ์์๋ณด์.

@Slf4j @RestController public class ApiExceptionController { @GetMapping("/api/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); } }
@ ์ค๋ฅํ์ด์ง ๋์ ์ค๋ฅ API๋ก ์ ํํ๊ธฐ
API ๋ฐ์ดํฐ๋ฅผ ์์ฒญํ๋๋ฐ HTML์ ๋ณด๋ด๋ฒ๋ฆฌ๋ฉด ์๋๋ค. ๋คํํ๋ ์คํ๋ง @RequestMapping ์ด๋ ธํ ์ด์ ์๋ HTP ํค๋๋ฅผ ๋ถ์ํด JSON API ์์ฒญ์ด ์์ ๋ ๋ค๋ฅธ ๊ฒฐ๊ณผ๋ฅผ ๋ฐํํ๋๋ก ๋ง๋ค ์ ์๋ค.
โก @RequestMapping(value = "/error-page/500", produces = MediaType.APPLICATION_JSON_VALUE)
// ErrorController ๋ฅผ ์ง์ ๊ตฌํํ ๊ฒฝ์ฐ @RequestMapping(value = "/error-page/500", produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity<Map<String, Object>> errorPage500Api( HttpServletRequest request, HttpServletResponse response) { log.info("API errorPage 500"); // RequestDispatcher.ERROR_EXCEPTION, ERROR_STATUS_CODE ์์ Exception ex = (Exception) request.getAttribute(ERROR_EXCEPTION); Map<String, Object> result = new HashMap<>(); result.put("status", request.getAttribute(ERROR_STATUS_CODE)); result.put("message", ex.getMessage()); Integer statusCode = (Integer) request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE); return new ResponseEntity<>(result, HttpStatus.valueOf(statusCode)); } }
๊ทธ๋ฌ๋ฉด HTML ์ค๋ฅ ํ์ด์ง์ ๊ฐ์ ๋ฉ์ปค๋์ฆ์ผ๋ก WAS๋ ์ปจํธ๋กค๋ฌ ๋ค์ ํธ์ถํ๋ค.

# ์คํ๋ง ๋ถํธ์ BasicErrorController
์์์ ์๋ฌ์ฉ ์ปจํธ๋กค๋ฌ๋ฅผ ๋ฐ๋ก ๋ง๋ค์ง์์๋, ์น ํ์ด์ง์์๋ BasicErrorController๋ฅผ ์ด์ฉํด ๊ฐ๋ฐ์๋ error/404.html๋ง ์์ฑํ๋ฉด ๊ฐ๋จํ๊ฒ ์ค๋ฅ ํ์ด์ง๋ฅผ ์ ๊ณต ํ ์์์๋ค. API๋ ์ด๋ป๊ฒ ์ฒ๋ฆฌํ ์ ์์๊น?

- ์ฌ์ค BasicErrorController๋ HTTP ์์ฒญ Header์ ์๋ Accept๋ฅผ ๋ณด๊ณ ์ค๋ฅํ์ด์ง๋ฅผ ์ ํํด์ค๋ค.
โก Accept = text/html ๋ผ๋ฉด 404.html์, Accept = application/json ์ด๋ผ๋ฉด Json ์๋ฌ๋ฅผ ๋ฐํํ๋ค.


- ์ด๋ BasicErrorController ์ฝ๋๋ฅผ ๋ณด๋ฉด ์ฝ๊ฒ ์ดํดํ ์ ์๋ค.
โก text/html์ด๋ผ๋ฉด ModelAndView๋ฅผ ๋ฐํํ์ง๋ง, ๊ทธ ์ธ์ ์์ฒญ์ ๋ํด์๋ ResponeEntity<>๋ฅผ ๋ฐํํ๋ค.

@ BasicController ๋ง์ผ๋ก๋ API ์ค๋ฅ ์ฒ๋ฆฌ์ ํ๊ณ๊ฐ ์๋ค.
HTML ์์ฒญ์ ์ค๋ฅํ์ด์ง๋ ๋จ์ํ๊ฒ 404, 500 ์ฝ๋์ ๋ฐ๋ผ ๋ค๋ฅธ ์ค๋ฅํ์ด์ง๋ฅผ ๋ฐํํด์ฃผ๋ฉด ๋์๋ค.
ํ์ง๋ง API๋ ์ ๋ง ๋ค์ํ ํํ๊ฐ ๋์จ๋ค. ํด๋ผ์ด์ธํธ, ์๋ฒ๋ง๋ค API ์คํ์ด ๋ค๋ฅผ์๋์๊ณ , ๊ฐ๋ค๊ณ ํ๋๋ผ๋ [Item API, Order API]๋ฑ ์์ฒญ๋ง๋ค ๋ค๋ฅธ ์ค๋ฅ JSON ๋ฉ์์ง๋ฅผ ์ ์กํด์ผ ํ๋ค. ์ด๋ฅผ ์ด๋ป๊ฒ ์ฒ๋ฆฌํ ์ ์์๊น?
# API ์์ธ์ฒ๋ฆฌ - HandlerExceptionResolver
์คํ๋ง MVC๋ ์ปจํธ๋กค๋ฌ ๋ฐ์ผ๋ก ์์ธ๊ฐ ๋ฐ์ํ ๊ฒฝ์ฐ, ์ด ์์ธ๋ฅผ ์ฒ๋ฆฌํ ์ ์๋ HandlerExceptionResolver ์ธํฐํ์ด์ค๋ฅผ ์ ๊ณตํด์ค๋ค. ๋ง์ฝ ์์ธ์ฒ๋ฆฌ๋ฅผ WAS๋ก ๋์ง์ง ์๊ณ ์ค๊ฐ์ ์ฒ๋ฆฌํ๊ณ ์ถ๋ค๋ฉด ์ด๋ฅผ ๊ตฌํํ๋ฉด ๋๋ค.
โก ExceptionResolver๋ฅผ ์ฌ์ฉํ๋ฉด ๊ตณ์ด WAS๋ฅผ ๊ฑฐ์ณ์ ๋์์ฌ ํ์๊ฐ ์๋ค. ๊ทธ๋ฅ ์์ธ๋ฅผ try-catch์ฒ๋ผ ์ก์ผ๋ฉด ๋๋ค.

- ์์ธ๊ฐ ๋ฐ์ํ ๊ฒฝ์ฐ ์ค๊ฐ์ ๋์์ฑ์ ์ง์ ํ ๋ฐํ๊ฐ์ด ๋์ค๋๋ก ๋ง๋ค ์ ์๋ค.
โก ExceptionResolver๋ ์ฌ๋ฌ๊ฐ ๋ฑ๋กํ ์ ์๋ค. ์ฆ null์ ๋ฐํํ๋ฉด ๋ค์ ๋ฆฌ์กธ๋ฒ๋ก ๋๊ธด๋ค.
@Slf4j public class MyHandlerExceptionResolver implements HandlerExceptionResolver { @Override public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { try { if (ex instanceof IllegalArgumentException) { // API์ ๊ฒฝ์ฐ ์ด๋ ๊ฒ ์๋ต๋ฐ์ดํฐ๋ฅผ ๋ณด๋ผ ์ ์์. // ์ง์ ๋ฉ์์ง๋ฅผ ์
๋ ฅํ๊ณ ์ถ๋ค๋ฉด => response.getWriter().println("hello"); response.sendError(HttpServletResponse.SC_BAD_REQUEST, ex.getMessage()); // View๊ฐ ํ์์๋ ๊ฒฝ์ฐ, ๊ทธ๋ฅ ๋น์ด์๋ ModelAndView๋ฅผ ๋ฐํํ๋ฉด ๋ทฐ๊ฐ ๋ ๋๋ง ๋์ง์์. return new ModelAndView(); } } catch (IOException e) { // response.writer ์์ ๋ฐ์ํ ์ ์๋ ์์ธ์ฒ๋ฆฌ } return null; // ๋จ ํด๋น ์์ธ(Exception ex)๋ฅผ ์ฒ๋ฆฌํ ๋ฆฌ์กธ๋ฒ๊ฐ ์๋ค๋ฉด WAS๋ก ๋๊น. } }
์ด๋ ๊ฒ ๋ง๋ ํธ๋ค๋ฌ๋ ํํฐ, ์ธํฐ์ ํฐ์ฒ๋ผ @Configuration์์ ๋ฑ๋กํ๋ฉด ๋๋ค.
@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) { // configureHandler...๋ฉ์๋๋ ๊ธฐ์กด์ ์คํ๋ง ์์ธํธ๋ค๋ฌ๋ฅผ ์ญ์ ํด๋ฒ๋ฆฐ๋ค. ์ด ๋ฉ์๋๋ฅผ ์ฌ์ฉํ์. resolvers.add(new MyHandlerExceptionResolver()); resolvers.add(new UserHandlerExceptionResolver()); } @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LogInterceptor()) .order(1) .addPathPatterns("/**") .excludePathPatterns("/css/**", "*.ico", "/error/**", "/error-page/**"); } }
# HandlerExceptionResolver ํ์ฉ๋ฐฉ๋ฒ
- ์์ธ์ํฉ์ ๋ฐ๋ผ ๋ค๋ฅด๊ฒ ์ฒ๋ฆฌํด์ผํ๋ฏ๋ก ์๋น์ค์์ ์ฌ์ฉํ Exception์ ์ ์ํ๋ค.
public class UserException extends RuntimeException { /* RuntimeException์ ์์ฑ์๋ง ์ค๋ฒ๋ผ์ด๋ฉ ํด์ฃผ๋ฉด ๋๋ค. ์ฝ๋๋ ์๋ต */ }
- ํด๋น ์์ธ๋ฅผ ์ฒ๋ฆฌํ ExceptionResolver๋ฅผ ๋ง๋ค๊ณ , @Configuration ๊ฐ์ฒด์ ๋ฆฌ์กธ๋ฒ๋ฅผ ์ถ๊ฐํ๋ค.
@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) { resolvers.add(new UserHandlerExceptionResolver()); } }
@Slf4j public class UserHandlerExceptionResolver implements HandlerExceptionResolver { private final ObjectMapper objectMapper = new ObjectMapper(); @Override public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { try { // ๋ด๊ฐ ๋ง๋ ์์ธ๊ฐ ๋ฐ์ํ ๊ฒฝ์ฐ if (ex instanceof UserException) { response.setStatus(HttpServletResponse.SC_BAD_REQUEST); // 400 ์๋ฌ๋ก ์ง์ String acceptHeader = request.getHeader("accept"); if ("application/json".equals(acceptHeader)) { // JSON ์๋ต์ธ ๊ฒฝ์ฐ Map<String, Object> errorResult = new HashMap<>(); errorResult.put("ex", ex.getClass()); errorResult.put("message", ex.getMessage()); // ๊ฐ์ฒด๋ฅผ ๋ฌธ์์ด(JSON)์ผ๋ก ๋ณ๊ฒฝํจ. String result = objectMapper.writeValueAsString(errorResult); // response body ๋ฅผ ์์ฑํจ. response.setContentType("application/json"); response.setCharacterEncoding("utf-8"); response.getWriter().write(result); return new ModelAndView(); } else { // ๊ทธ๋ฅ HTML ์๋ต์ ์๊ตฌํ ๊ฒฝ์ฐ return new ModelAndView("error/500"); } } } catch (IOException e) { // response.writer ์์ ๋ฐ์ํ ์ ์๋ ์์ธ์ฒ๋ฆฌ } return null; // ๊ทธ ์ธ ์๋ฌ๋ ๋ค์ ๋ฆฌ์กธ๋ฒ(์๋ค๋ฉด WAS)์ ๋๊น. } }
์์ธ์ฒ๋ฆฌ๋ฅผ ํ ์ ์๋๊ฑด ์ข์ง๋ง ๋งค๋ฒ ๋ฆฌ์กธ๋ฒ๋ฅผ ๋ง๋ค์ด ์ฌ์ฉํ๊ธฐ์๋ ๋ฒ๊ฑฐ๋กญ๋ค. ๋ ํธํ๊ฒ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ์์๊น?
# ์คํ๋ง๋ถํธ์ ExceptionResolver
์ญ์๋ ์คํ๋ง ๋ถํธ์์๋ ์ฌ์ฉํ๊ธฐ ํธํ๊ฒ ์๋์ ExceptionResolver๋ฅผ ๋ฏธ๋ฆฌ ๋ง๋ค์ด๋๊ณ ์๋์ผ๋ก ๋ฑ๋กํด์ค๋ค.
์๋ 3๊ฐ์ ๊ธฐ๋ณธ ๋ฆฌ์กธ๋ฒ๊ฐ ์์๋๋ก ๋์ํ๋ค. ํ์ฌ ๋ฆฌ์กธ๋ฒ์์ null์ ๋ฐํํ๋ ๊ฒฝ์ฐ ๋ค์ ๋ฆฌ์กธ๋ฒ๊ฐ ์คํ๋๋ค.
- 1. ExceptionHandlerExceptionResolver
โก ์คํ๋ง์ @ExceptionHandler๋ฅผ ์ฒ๋ฆฌํ๋ ๋ฆฌ์กธ๋ฒ์ด๋ค. ์คํ๋ง API ์์ธ๋ ๋๋ถ๋ถ ์ด๊ฑธ ์ฌ์ฉํ๋ค. - 2. ResponseStatusExceptionResolver
โก @ResponseStatus(HttpStatus.NOT_FOUND) ๊ฐ์ ๊ฑธ ์ฝ์ด ์์ธ์ ๋ฐ๋ผ HTTP ์ํ์ฝ๋๋ฅผ ๋ณ๊ฒฝํ๋ ๋ฆฌ์กธ๋ฒ์ด๋ค.
โก ์ฐธ๊ณ ๋ก reason์ ์คํ๋ง์ ๋ฉ์์ง("error.bad")๋ฅผ ์ด์ฉํ ์๋ ์๋ค. ๋ฑ๋ก๋๊ฒ ์๋ค๋ฉด ๊ทธ๋ฅ ๋ฌธ์์ด๋ก ์ฒ๋ฆฌํ๋ค.

- 3. DefaultHandlerExceptionResolver ( ์ฐ์ ์์๊ฐ ๊ฐ์ฅ ๋ฎ์ ๋ฆฌ์กธ๋ฒ )
โก ๊ทธ ์ธ ์คํ๋ง ๋ด๋ถ์ ๋ฐ์ํ ์ ์๋ ์์ธ๋ค์ ์ฒ๋ฆฌํ๋ ๊ธฐ๋ณธ ๋ฆฌ์กธ๋ฒ์ด๋ค.
โก ์) ์์ฒญ ๋ฉ์์ง ์๋ฃํ์ด ์ด์ํด์(TypeMismatchException) ์ฒ๋ฆฌ๊ฐ ๋ถ๊ฐ๋ฅํ ๊ฒฝ์ฐ HTTP 400์ ๋ฐํํ๋ค.
- @ResponseStatus(...)์ ๊ฒฝ์ฐ ์ฝ๋๋ฅผ ์ง์ ์์ ํด์ผ ์ฌ์ฉ๊ฐ๋ฅํ๋ค๋ ๋จ์ ์ด ์๋ค.
- ์ฐธ๊ณ ๋ก @ResponseStatus(...)๋ฅผ ์ฌ์ฉํ๋ฉด, ๊ธฐ์กด ์์ธ๋ฅผ ResponseStatusException์ผ๋ก ๊ฐ์ธ์ ๋ฆฌ์กธ๋ฒ๊ฐ ์ฒ๋ฆฌํ๋๋ก ๋ง๋ ๋ค. ์ฆ ์ด๋ ธํ ์ด์ ์ ์ฌ์ฉํ์ง ์๊ณ ์ง์ throw Resp...Exception์ ๋์ ธ๋ ํด๋น ๋ฆฌ์กธ๋ฒ๊ฐ ์์ธ๋ฅผ ์ฒ๋ฆฌํด์ค๋ค.
# API ์ฒ๋ฆฌ์ @ExceptionHandler
ํ๋ฒ ๋ ๋ณต์ตํ์๋ฉด, HTML ํ๋ฉด ์ค๋ฅ๋ ๊ทธ๋ฅ BasicErrorController๋ฅผ ์ด์ฉํด์ 404 ํ์ด์ง๋ฅผ ๋ณด์ฌ์ฃผ๋ฉด ํด๊ฒฐ๋๋ค.
ํ์ง๋ง API์ ๊ฒฝ์ฐ์๋ ๋ค๋ฅด๋ค. Item-API, Order-API๋ฑ ์๋น์ค ์ข ๋ฅ์ ๋ฐ๋ผ ์ค๋ฅ ๋ฉ์์ง๋ฅผ ๋ค๋ฅด๊ฒ ์ฒ๋ฆฌํด์ค์ผํ๊ณ ์ฌ์ง์ด๋ ํด๋ผ์ด์ธํธ์ ๋ฐ๋ผ API์ ์คํ ์์ฒด๊ฐ ๋ฌ๋ผ์ง๊ธฐ๋ ํ๋ค. ๋ฌผ๋ก ํ์ํ๋ค๋ฉด HTML ํ๋ฉด๋ ๋ฆฌ์กธ๋ฒ๋ก ์ฒ๋ฆฌํด๋ ๋๋ค.
โก ๊ทธ๋์ API์ ์์ธ์ฒ๋ฆฌ๋ ๋ณดํต ์ด๋
ธํ
์ด์
์ ์ด์ฉํ ExceptionHandlerExceptionResolver๋ฅผ ์ด์ฉํ๋ค.
โก ๋ฌผ๋ก ๋ฆฌ์กธ๋ฒ๋ ์์๋๋ก ์คํ๋๊ธฐ ๋๋ฌธ์, ๋ค๋ฅธ ExceptionResolver๋ฅผ ํจ๊ป ์ฌ์ฉํ ์ ์๋ค.
@ExceptionHandler ์ด๋ ธํ ์ด์ ์ฌ์ฉ
ExceptionResolver๋ ์์ธ๊ฐ ๋ฐ์ํ๋ฉด, ์ปจํธ๋กค๋ฌ ์์ @ExceptionHandler ๋ฅผ ์ฌ์ฉํ๋ ๋ฉ์๋๊ฐ ์๋์ง ํ์ธํ๋ค.
๊ทธ๋ฆฌ๊ณ ์์ธ๋ฅผ ํด๋น ๋ฉ์๋๊ฐ ์ฒ๋ฆฌํ๋๋ก ๋ง๋ค์ด์ฃผ๊ณ HTTP ์์ฒญ์ ์ ์ ์๋ต(200)ํ๋ค.
โก ๋ง์ฝ HTTP ์ค๋ฅ์ฝ๋๋ฅผ 400์ ๋ฐํํ๊ณ ์ถ๋ค๋ฉด ์์ธ์ฒ๋ฆฌ ๋ฉ์๋์ @ResponseStatus๋ฅผ ์ถ๊ฐ๋ก ์ฌ์ฉํ๋ฉด ๋๋ค.
@Slf4j @RestController public class ApiExceptionV2Controller { // ์์ธ๊ฐ ๋ฐ์ํ๋ Mapping ๋ฉ์๋ @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); } @ResponseStatus(HttpStatus.BAD_REQUEST) @ExceptionHandler(IllegalArgumentException.class) public ErrorResult illegalExHandle(IllegalArgumentException e) { return new ErrorResult("BAD", e.getMessage()); } @ExceptionHandler public ResponseEntity<ErrorResult> userExHandle(UserException e) { ErrorResult errorResult = new ErrorResult("USER-EX", e.getMessage()); return new ResponseEntity<>(errorResult, HttpStatus.BAD_REQUEST); } }
@ExceptionHandler์ ์ถ์ํ
๋๋๊ฒ๋ ์คํ๋ง์ @ExceptionHandler๋ SpringMVC ์ปจํธ๋กค๋ฌ์ฒ๋ผ ์ถ์ํ๋์ด์๋ค. ๊ทธ๋์ ์๋์ ์์๋ค์ฒ๋ผ ํ๋ผ๋ฉํ, ๋ฆฌํดํ์ ์ ๋ค์ํ๊ฒ ๊ฐ์ง ์ ์๋ค. ์๋ง ์์ํ๊ฒ ์ง๋ง, DispatcherServlet์์ @ExceptionHandler ๋ํ ๋ฆฌ์กธ๋ฒ๋ค์ ์ด์ฉํด ์ถ์ํํ๋ค.
- ์์ ์์ ์ฒ๋ผ ํ๋ผ๋ฉํ์ ํ์ ์ด ์์ธ ํ์ ๊ณผ ๊ฐ๋ค๋ฉด @ExceptionHandler( ์์ธ.class ) ๋ฅผ ์๋ตํด๋ ๋๋ค.
@ExceptionHandler // @ExceptionHandler(UserException.class)์ ๊ฐ์ ๋์ public ResponseEntity<ErrorResult> userExHandle(UserException e) { ErrorResult errorResult = new ErrorResult("USER-EX", e.getMessage()); return new ResponseEntity<>(errorResult, HttpStatus.BAD_REQUEST); }
- ExceptionResolver๋ ํด๋น ์์ธ์ ์์ ๊ฐ์ฒด๊น์ง ์ฒ๋ฆฌํ๋ค. ์ฆ ์๋์ ๊ฐ์ default ์์ธ์ฒ๋ฆฌ๋ฅผ ๋ง๋ค ์ ์๋ค.
โก ๋ค๋ง @ExceptionHandler๊ฐ [๋ถ๋ชจ, ์์] ์์ธ๊ฐ ๋ค ์๋ค๋ฉด ๋ ๋ํ ์ผํ ๊ฐ์ฒด, ์ฆ ์์ ์์ธ๋ถํฐ ๋จผ์ ๋งคํํ๋ค.
// ๋ฉ์๋ ์ ์ธ ์์๋ ์๊ด์๋ค. ๋ ์์ธํ(์์) ์์ธ๋ถํฐ ๋ฉ์๋๊ฐ ๋งคํ๋๋ค. // ๋ชจ๋ Exception์ ๋ฐ๋ ์์ธ์ฒ๋ฆฌ. ์ค์๋ก ๋์น๋ ์์ธ ๋ฐ์์ ๋ฐฉ์งํด์ค๋ค. @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) @ExceptionHandler public ErrorResult exHandle(Exception e) { log.error("[exceptionHandle] ex", e); return new ErrorResult("EX", "๋ด๋ถ ์ค๋ฅ"); }
- ๋ฌผ๋ก ์ฌ๋ฌ ๊ฐ์ ์์ธ๋ฅผ ํ๋์ ๋ฉ์๋์ ์ง์ ํ ์๋ ์๋ค.
@ExceptionHandler( {AException.class, BException.class} ) public String ex(Exception e) { log.info("exception e", e); }
- ์์๋ ์ธ๊ธํ์ง๋ง, ๊ผญ API์๋ง ์ฌ์ฉํ๋ผ๋ ๋ฒ์ ์๋ค. ์๋์ ๊ฐ์ด View๋ฅผ ๋ฐํํด์ค๋ ๋๋ค.
โก ๋ฌผ๋ก @ResponseBody๋ @RestController ํ๊ทธ๊ฐ ์์ผ๋ฉด ๋ทฐ ๋ฆฌ์กธ๋ฒ๊ฐ ์คํ๋์ง์์ ์ฌ์ฉํ ์ ์๋ค.
// ๋ค๋ง API ์ปจํธ๋กค๋ฌ์ @RestController๋ฅผ ๋ถ์ด๋ฏ๋ก, ๊ฑฐ์ ์ฌ์ฉํ์ง ์๋ ๋ฐฉ๋ฒ์ด๊ธดํ๋ค. @ExceptionHandler(ViewException.class) public ModelAndView ex(ViewException e) { log.info("exception e", e); return new ModelAndView("error"); // ๋ทฐ ๋ฆฌ์กธ๋ฒ ์คํ๋จ }
# @ControllerAdvice๋ฅผ ์ด์ฉํ API ์์ธ์ฒ๋ฆฌ
@ExceptionHandler๋ ๋๋ฌด๋ ํธ๋ฆฌํ ๊ธฐ๋ฅ์ด์ง๋ง, ํด๋น ์ปจํธ๋กค๋ฌ ๊ฐ์ฒด์๋ง ์ ์ฉ๋๋ค๋ ๋จ์ ์ด ์๋ค.
์ฆ ์ปจํธ๋กค๋ฌ๋ง๋ค @ExceptionHandler๋ฅผ ํ๋ํ๋ ์์ฑํด์ค์ผํ๋๋ฐ, ์ด๋ฅผ ๋ชจ๋ ์ปจํธ๋กค๋ฌ ๊ณตํต์ผ๋ก ๋ฌถ์ ์ ์์๊น?
โก Spring AOP์์ ์ ๊ณตํด์ฃผ๋ @ControllerAdvice, @RestControllerAdvice ๋ฅผ ์ด์ฉํ๋ฉด ๋๋ค.
* ์ฐธ๊ณ ๋ก @RestControllerAdvice๋ ๊ทธ๋ฅ ๊ธฐ์กดํ๊ทธ์ @ResponseBody๊ฐ ์ถ๊ฐ๋ก ๋ถ์ด์๋ ํ๊ทธ์ด๋ค. ์ฆ return์ ํ์ ๋ ๋ทฐ๋ฆฌ์กธ๋ฒ๊ฐ ๋์ํ์ง ์๋๋ค.
๐ ErrorResult ๐ ExControllerAdvice โก @ExceptionHandler ๋ถ๋ถ์ ์ฝ๋๋ฅผ ๋ถ๋ฆฌํด ๋ฐ๋ก ์์ฑํ๋ค.
@Data @AllArgsConstructor public class ErrorResult { private String code; private String message; }
@Slf4j @RestControllerAdvice(basePackages = "hello.exception.api") public class ExControllerAdvice { @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 exHandler(Exception e) { log.error("[exceptionHandler] ex", e); return new ErrorResult("EX", "๋ด๋ถ ์ค๋ฅ"); } }
๊ทธ๋ฅ AOP๋ณด๋ค ์ฌ์ฉ๋ฒ์ด ๋ ๊ฐ๋จํ๋ค. JoinPoint ๊ฐ์ ๊ฑฐ ํ์์์ด @ControllerAdvice๋ง ๋ถ์ฌ์ฃผ๋ฉด ๋๋ค.
- (basePackages ="...")๋ฅผ ์ง์ ํด์ฃผ๋ฉด ํด๋น ํจํค์ง์์ ์๋ ๋ชจ๋ @Controller์ ์ ์ฉ๋๋ค.
- ๋ฐ๋ก ์ ์ง์๋๋ค๋ฉด ํ๋ก์ ํธ ๊ฒฝ๋ก์ ์๋ ๋ชจ๋ ์ปจํธ๋กค๋ฌ์ ์ ์ฉ๋๋ค. (Spring ๊ณต์๋ฌธ์)

์ด๋ฅผ ์ฌ์ฉํ๋ฉด ์ด์ ์ปจํธ๋กค๋ฌ์์ ์์ธ์ฝ๋๋ฅผ ์์ฑํ์ง ์์๋ ๋๋ค. ๊ฐ์ฒด์ ์ฑ ์์ด ๋ถ๋ฆฌ๋์๋ค.
@Slf4j @RestController public class ApiExceptionV3Controller { @GetMapping("/api3/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); } } // ์ฐธ๊ณ ๋ก MemberDto๋ ์ด๋ ๊ฒ ์๊ฒผ๋ค. @Data @AllArgsConstructor class MemberDto { private String memberId; private String name; }
๋ธ๋ก๊ทธ์ ์ ๋ณด
JiwonDev
JiwonDev