들어가기에 앞서
Spring에서 예외 발생 시 전체적인 흐름 및 에러 페이지 처리에 대한 내용은 이전 글에서 다루고 있으므로 이전 글을 참고해 주세요.
링크 : Spring MVC - 예외 처리(Error page)
에러 페이지 처리와 다른 점
뷰 템플릿 또는 정적 페이지로 예외 처리를 하는 경우에는 Spring의 도움을 받는다면 HTML 파일만 추가하여 손쉽게 해결할 수 있었다.
(예외 처리를 직접 커스터마이징 하는 것은 다른 이야기)
하지만 이는 HTML 페이지를 전달할 때의 이야기이고.. API의 경우 예외 발생 시 HTML 페이지를 전달하면 안 되고 예외에 따른 JSON 포맷의 응답 데이터를 반환해야 한다. 추가로 HTTP 응답 코드도 적절하게 설정해야 할 것이다.
결국 HTML 페이지로 예외 처리를 할 때 보다 생각하고 신경 써야 하는 부분이 많다는 이야기가 된다.
API 예외 처리
이제 API 사용 시 예외 처리 방법이 에러 페이지 처리와는 확연히 다르다는 것을 알았으니 어떻게 처리하면 되는가에 대해 알아보자
API의 예외를 처리하는 방법은 크게 2가지로 나눠볼 수 있다.
- Servlet 처리 : 에러 페이지 처리와 동일한 방식
- WAS(HTTP 요청) -> Controller(예외 전달) -> WAS(예외 페이지 요청) -> Controller(예외 페이지 호출)
- Spring boot 처리 : Spring boot에서 제공하는 예외 처리를 사용하는 방식
- Servlet 처리 중간에 Spring boot에서 제공하는 예외 처리 동작이 들어가는 것뿐
- Filter와 Interceptor 같은 느낌으로 Servlet 동작 안에서 Spring이 무언가 추가로 처리를 하는 개념
바로 위에서 2가지라고 했지만 사실 동작의 흐름을 보면 1가지이다. Servlet 처리 동작에 맞춰 Spring이 처리를 해주는 것뿐이다. 그런데 2가지라고 나누었던 이유는 편하게 구분하기 위함이다.
그럼 먼저 기본이 되는 Servlet의 에러 페이지 처리와 동일한 방식부터 알아보도록 하겠다.
Servlet 에러 페이지 동작
WAS까지 예외가 도착한 후 에러 페이지를 호출하는 동작과 동일한 흐름으로 동작하기 때문에 해당 기능을 구현하고자 한다면 기존에 에러 페이지를 호출하는 컨트롤러에서 API 에러를 반환하는 로직을 구현하면 된다.
아래의 이미지에서 WAS의 에러 페이지 요청 부분을 API 에러 메시지 요청으로 이해하면 된다. (따라서 렌더링하는 부분도 스킵)
해당 내용은 설명보다는 코드로 보는 것이 이해가 쉽기 때문에 바로 예제 코드로 알아보도록 하겠다.
import java.util.HashMap;
import java.util.Map;
import javax.servlet.RequestDispatcher;
import javax.servlet.http.HttpServletRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class ErrorPageController {
// ...
// HTML 에러 페이지를 반환하는 코드
@RequestMapping("/error-page/500")
public String errorPage500() {
return "error-page/500";
}
// API 예외 처리를 위한 코드, 위와 같은 경로지만 'produces' 옵션을 사용하여 디테일하게 정의
@RequestMapping(value = "/error-page/500", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Map<String, Object>> errorApi500(HttpServletRequest request) {
// API 예외 전달을 위한 Map
Map<String, Object> result = new HashMap<>();
// 예외를 사용하기 위해 받아옴
Exception ex = (Exception) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
// 에러 상태 코드를 반환하기 위해 저장
result.put("status", request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE));
// 에러 메시지를 반환하기 위해 저장
result.put("message", ex.getMessage());
// HTTP 상태 코드를 사용하기 위해 받아옴
Integer statusCode = (Integer) request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
// statusCode를 HTTP 상태 코드로 보내며 Body에 result를 JSON 포맷으로 담아서 반환
return new ResponseEntity<>(result, HttpStatus.valueOf(statusCode));
}
}
위 코드를 보면 동일한 경로인 "/error-page/500"에 대해 2가지의 다른 로직을 수행하도록 구현한 것을 볼 수 있다.
이렇게 구현이 가능한 이유는 "produces" 옵션을 사용하여 HTTP 요청의 Accept 헤더에 따라 동작하도록 설정을 잡아줬기 때문이다.
따라서 요청의 Accept 헤더가 "application/json"으로 들어오면 아래의 로직이 동작하게 되어 JSON 포맷의 데이터로 반환이 될 것이고, 그 외 다른 요청에 대해선 위 로직이 발생하여 "error-page/500"의 HTML 에러 페이지가 반환될 것이다.
Spring boot 기본 지원
Servlet 에러 페이지 동작의 경우 Spring boot에서 제공하는 "BasicErrorController"를 통해 쉽게 사용이 가능했었는데, 그렇다면 'API 에러 처리도 지원해 주지 않을까?'라는 의문이 들 수 있다. 이에 대한 의문에 답변을 하자면 "맞다. Spring boot가 API 에러 처리도 지원을 해주고 있다."이다. API 예외 처리에 대한 지원도 "BasicErrorController"에서 위 예제 코드와 동일한 방식으로 HTML 에러 페이지와 API 요청에 대한 응답을 나누어 지원하고 있다. (예제 코드와 차이점이라면 BasicErrorController에선 produces 옵션 설정이 "text/html"인 경우에만 HTML 에러 페이지를 반환하고, 그 외 모든 요청에 대해선 JSON 포맷으로 예외를 반환하도록 되어있다는 점)
하지만 API의 경우 예외 발생 시 반환하는 에러 메시지 포맷이 규약으로 정해져있는 것이 아니다 보니 상황에 따라 변하게 된다. 또한 위 이미지에선 "Not Found"에러에 맞는 HTTP status code인 404를 잘 전달해 주었지만, 예외에 따른 HTTP status code를 지정하고 싶은 경우가 발생할 수 있다.
즉, Spring boot에서 제공해 주는 API 예외 기능을 그대로 사용하기엔 무리가 있다는 이야기다.
HandlerExceptionResolver
위와 같은 문제가 있다면 해결 방법 또한 있을 것이다. 이미 Spring boot에서는 예외에 따른 메시지 포맷과 HTTP status code를 커스터마이징 할 수 있는 기능을 제공해 주고 있으며, 이 기능이 바로 "HandlerExceptionResolver"다.
위 이미지와 같은 흐름으로 동작을 하게되며, HandlerExceptionResolver를 어떻게 구현하는가에 따라 3가지 동작이 가능하다.
- sendError(...) 메서드 & return new ModelAndView() 사용
- 에러 페이지 요청과 동일한 동작 수행
- 따라서 위 이미지에서 WAS가 Controller로 에러 페이지를 요청하는 흐름이 추가됨
- Json & return new ModelAndView() 사용
- WAS 입장에선 예외를 전달받지 않았기 때문에 정상 동작으로 처리되어 Controller에 다른 요청 X
- response를 직접 제어하여 Json 데이터 전달하도록 설정 (API 예외 처리 방법)
- ModelAndView 객체에 데이터를 넣어 사용
- WAS 입장에선 예외를 전달받지 않았기 때문에 정상 동작으로 처리되어 Controller에 다른 요청 X
- ModelAndView 객체에 들어있는 데이터에 맞춰 View를 렌더링하여 반환
위 동작들에 대한 구현 방법을 예제 코드를 통해 알아보도록 하겠다.
HandlerExceptionResolver 구현 코드 (필수 : 주석 참조)
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
@Slf4j
public class TestExceptionResolver implements HandlerExceptionResolver {
// Json format 변환용
private final ObjectMapper objectMapper = new ObjectMapper();
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) {
try {
if (ex instanceof IllegalArgumentException) {
// Case 1 : sendError() 메서드 & return new ModelAndView() 사용
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
// return new ModelAndView();
// Case 2 : Json & return new ModelAndView() 사용
if (request.getHeader("accept").equals(MediaType.APPLICATION_JSON_VALUE)) {
// Error data 설정
Map<String, Object> data = new HashMap<>();
data.put("status", HttpServletResponse.SC_BAD_REQUEST);
data.put("exception", ex.getClass());
data.put("message", ex.getMessage());
// Convert to json format
String jsonData = objectMapper.writeValueAsString(data);
// HTTP status code 설정
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
// 응답 헤더 설정
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setCharacterEncoding("utf-8");
// Json data 설정
response.getWriter().write(jsonData);
return new ModelAndView();
} else {
// Case 3 : ModelAndView 객체에 데이터를 넣어 반환하여 View를 렌더링 하도록 요청
return new ModelAndView("error-page/" + HttpServletResponse.SC_BAD_REQUEST);
}
}
} catch (IOException e) {
// sendError() 예외 처리를 위함
log.error("resolveException exception", e);
}
/**
* return type
* null : 다음 HandlerExceptionResolver 호출
* new ModelAndView() : View 관련 데이터가 없기에 렌더링 하지 않고 response 설정에 따른 데이터를 반환 (sendError() 또는 API 예외 처리에 사용)
* new ModelAndView(...) : 지정한 View를 렌더링하고 종료
*
* ModelAndView 객체를 반환하면 WAS 입장에서 정상 처리 되었다고 인식
* 즉, 예외가 WAS까지 전달이 안되며 그렇기에 에러 페이지 요청 동작 X
*/
return null;
}
}
HandlerExceptionResolver 등록 코드
import java.util.List;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
public class WebConfig implements WebMvcConfigurer {
@Override
public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
// 아래와 같이 추가를 해주면 등록 완료
resolvers.add(new TestExceptionResolver());
}
// 아래와 같이 "configureHandlerExceptionResolvers" 메서드도 존재
// 해당 메서드를 사용하여 위와 동일하게 추가가 가능하지만 큰 차이점 존재
// 해당 메서드를 사용하게 되면 Spring boot에서 기본적으로 등록하는 HandlerExceptionResolver가 등록이 안됨
// 따라서 특별한 경우가 아니라면 위 메서드 사용
@Override
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
// Spring boot가 기본적으로 제공하는 HandlerExceptionResolver를 사용하지 않으려면 해당 메서드 사용
resolvers.add(new TestExceptionResolver());
}
}
Spring boot가 제공하는 HandlerExceptionResolver
직접 HandlerExceptionResolver를 구현하여 사용하는 방법을 알아보았으나 API 예외 처리 과정이 상당히 귀찮다는 것을 알 수 있다.
또한 등록 과정에서 Spring boot가 기본적으로 제공해 주는 HandlerExceptionResolver가 있다는 것을 암시하기도 했다.
그 말은 즉, Spring boot가 이미 좀 더 쉬운 기능들을 제공하고 있다는 이야기가 될 것이다.
Spring boot에서 기본적으로 제공하고 있는 HandlerExceptionResolver는 다음과 같이 3개가 있으며 우선순위는 다음과 같다.
- ExceptionHandlerExceptionResolver : @ExceptionHandler를 처리하는 ExceptionResolver
- ResponseStatusExceptionResolver : @ResponseStatus와 ResponseStatusException를 처리하는 ExceptionResolver
- DefaultHandlerExceptionResolver : 스프링이 정한 예외에 대한 기본적인 처리를 하는 ExceptionResolver
여기서 가장 중요한 ExceptionResolver는 우선순위를 보면 알 수 있겠지만.. 바로 ExceptionHandlerExceptionResolver다.
가장 중요한 이유라고 한다면.. 가장 많이 쓰이기 때문이라고 볼 수 있겠다. (실무에서 대부분 해당 기능을 사용하여 구현)
그럼 우선순위가 낮은 순서부터 알아보도록 하겠다.
DefaultHandlerExceptionResolver
Spring boot가 제공하는 HandlerExceptionResolver 중 가장 우선순위가 낮은 ExceptionResolver다.
해당 ExceptionResolver가 제공하는 기능은 간단하며, 이미 위에서 해당 기능이 동작한 것을 확인할 수 있다.
"Spring boot 제공 기본 API 예외 포맷" 이미지를 보면 별다른 설정을 하지 않았는데 HTTP status code가 500(Internal Server Error)가 아닌 404(Not Found)로 지정된 것을 볼 수 있다. 즉, DefaultHandlerExceptionResolver의 경우 이렇게 예외에 맞는 HTTP status code를 설정해 주는 역할을 한다.
ResponseStatusExceptionResolver
Spring boot가 제공하는 HandlerExceptionResolver 중 우선순위가 중간에 위치한 ExceptionResolver다.
@ResponseStatus 어노테이션 및 ResponseStatusException 예외에 대한 처리를 하는 ExceptionResolver다.
어노테이션 및 예외를 사용해 HTTP status code를 지정하는 것이 주 역할이며, 이는 간단한 예제 코드를 통해 알아보도록 하겠다.
// 예외 추가 및 @ResponseStatus 설정
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(code = HttpStatus.BAD_REQUEST, reason = "잘못된 인수")
public class BadRequestEx extends IllegalArgumentException {
}
----
// 위에서 추가한 예외를 발생시키는 테스트용 컨트롤러
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.server.ResponseStatusException;
@Controller
public class TestController {
// @ResponseStatus 어노테이션을 적용한 사용자 정의 예외 발생
@GetMapping("/test/bad-request")
public String badRequestException() {
throw new BadRequestEx();
}
// ResponseStatusException 예외를 사용하여 예외 발생
@GetMapping("/test/bad-request2")
public String badRequestException2() {
// ResponseStatusException(HTTP 상태 코드, 에러 메시지, 발생한 예외);
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "error.not-found", new IllegalArgumentException());
}
}
위 코드와 포스트맨 요청 결과를 보면 바로 이해가 가능할 것이다.
사용자 정의 예외에 어노테이션을 사용하여 해당 예외가 발생하는 경우 지정한 HTTP status code가 발생하도록 지정하거나,
ResponseStatusException을 사용하여 지정한 HTTP status code가 발생하도록 설정할 수 있다.
그리고 위 예제 코드를 보면 ResponseStatusException를 사용한 예제에서 2번째 파라미터에 "error.not-found"를 인자로 사용한 것을 확인 할 수 있다.
이는 ResponseStatusExceptionHandler가 "/resources/messages.properties"를 참고하여 메시지를 찾을 수 있는 기능을 제공하기 때문에 사용 가능한 방법이다. 이렇게 ResponseStatusExceptionHandler를 사용하여 추가한 에러 메시지를 기본 제공 API 예외 메시지에 포함시키고 싶다면 "application.properties"에 "server.error.include-message=always" 설정을 잡아줘야 message 항목이 추가되어 확인이 가능해진다.
ExceptionHandlerExceptionResolver
이전까지의 Spring boot에서 기본적으로 제공해 주는 HandlerExceptionResolver들의 기능을 보면 API 예외 메시지에 대한 처리라기보다는 HTTP status code를 손쉽게 제어할 수 있도록 지원하는 기능이라는 것을 알 수 있다. 또한 동일한 예외라고 하더라도 특정 컨트롤러에서는 다른 동작을 하도록 지정하고 싶을 수 있을 텐데 이러한 경우에 대한 처리가 힘들어 보인다.
그럼 결국 사용자 API 예외 메시지에 대한 처리는 HttpServletResponse에 직접 이것저것 설정을 잡아주고 data도 직접 Json fotmat으로 만들어 사용해야 하는 것일까? 그리고 특정 컨트롤러에 다른 처리는 어떻게 해야 할 수 있을 것인가?
이러한 의문점과 불편함을 해결해 주는 HandlerExceptionResolver가 바로 ExceptionHandlerExceptionResolver라고 할 수 있다.
바로 예제 코드로 사용법과 동작을 알아보도록 하겠다.
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RestController
public class TestController {
// 예외 발생 시 에러 메시지 전달용 클래스
@Data
@AllArgsConstructor
public class ErrorForm {
private String code;
private String message;
}
// @ExceptionHandler(처리할 예외 클래스) 사용
// @ResponseStatus를 이용하여 HTTP 상태 코드 지정
// 참고로 이러한 경우에 @ResponseStatus 어노테이션의 처리는 ExceptionHandlerExceptionResolver 내에서 처리한다.
// HandlerExceptionResolver의 동작을 생각해 보면 당연한 이야기 (예외 처리를 정상적으로 했다면 다음 HandlerExceptionResolver를 호출하지 않는다)
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(IllegalArgumentException.class)
public ErrorForm illegalArgumentExceptionHandler(IllegalArgumentException ex) {
log.info("illegalArgumentExceptionHandler call", ex);
return new ErrorForm(HttpStatus.BAD_REQUEST.toString(), "잘못된 인수 전달");
}
// @ExceptionHandler 사용, 처리할 예외가 인자로 들어가 있다면 어노테이션에서 생략 가능
// ResponseEnctity<>를 이용하여 HTTP 상태 코드 지정
@ExceptionHandler
public ResponseEntity<ErrorForm> badRequestExceptionHandler(BadRequestException ex) {
log.info("badRequestExceptionHandler call", ex);
ErrorForm errorForm = new ErrorForm(HttpStatus.I_AM_A_TEAPOT.toString(), "나는 주전자입니다. 테스트");
return new ResponseEntity<>(errorForm, HttpStatus.I_AM_A_TEAPOT);
}
// 아래와 같이 @ExceptionHandler 어노테이션에 여러개의 예외를 지정하여 하나의 로직으로 다중 예외 처리 가능하다
// @ResponseStatus(HttpStatus.BAD_REQUEST)
// @ExceptionHandler({IllegalArgumentException.class, BadRequestException.class})
// public ErrorForm illegalArgumentExceptionHandler(RuntimeException ex) {
// 여러 개의 예외에 대한 처리 시 인자로는 해당 예외들을 전부 받을 수 있는 상위 클래스를 사용해야 한다
// log.info("illegalArgumentExceptionHandler call", ex);
// return new ErrorForm(HttpStatus.BAD_REQUEST.toString(), "잘못된 인수 전달");
// }
@GetMapping("/test/exception-1")
public String badRequestException() {
throw new BadRequestException();
}
@GetMapping("/test/exception-2")
public String badRequestException2() {
throw new IllegalArgumentException();
}
}
예제 코드만 보더라도 얼마나 쉽고 간편하게 예외에 따른 동작을 설정할 수 있는지 알 수 있다.
그냥 "@ExceptionHandelr" 어노테이션을 사용하여 어떠한 예외에 동작할지 지정 후 Controller 코드처럼 구현하면 끝이다.
이렇게 구현한 ExceptionHandelr 예외 처리 동작은 해당 컨트롤러에만 적용이 되기 때문에 컨트롤러 단위로 세세하게 예외 처리가 가능하다는 장점도 있다.
그렇다면 글로벌 설정은 어떻게 해야하지? 그리고 컨트롤러 코드에 예외 처리 코드가 함께 존재하기에 이를 분리할 수 있는 방법은 없나? 라는 의문이 생길 수 있다. 이러한 문제를 해결할 수 있는 방법 또한 이미 Spring boot에서 제공을 하고 있다.
(추가로 상위 클래스를 사용하는 경우 하위 클래스 예외도 전부 처리 가능하다, 상위 클래스와 하위 클래스 예외를 둘 다 지정한 경우에는 더 디테일한 하위 클래스 예외에 대한 처리가 우선시 되어 처리된다.)
ControllerAdvice
Spring boot에서는 @ControllerAdvice, @RestControllerAdvice라는 어노테이션을 제공하고 있다. 두 어노테이션의 차이는 @Controller와 @RestController의 차이와 같다. (@ResponseBody 여부 차이)
@ControllerAdvice 어노테이션이 제공하는 기능은 Controller에서 예외 처리 코드의 분리 및 글로벌 설정이다.
이 또한 간단하므로 바로 예제 코드를 통해 알아보도록 하겠다.
글로벌 설정 및 특정 컨트롤러 지정 동작을 테스트하기 위해서 코드가 조금 길지만 중복된 내용이 많다.
Controller의 경우 로직은 동일하지만 요청 경로만 다르고, ControllerAdvice를 2개로 나누어 만들어 사용을 하다보니 위에서 사용한 에러 메시지 전달 클래스는 따로 빼서 구현했다.
글로벌 예외 설정에 걸리면 "406 Not Acceptable" 상태 코드를 반환하고, 특정 컨트롤러에 대한 예외 설정에 걸리면 위 예제와 동일한 예외 처리 로직으로 동작하도록 하였다.
TestController 1 & 2 (클래스명과 요청 경로만 다르다)
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestController {
@GetMapping("/test1/exception-1")
public String badRequestException() {
throw new BadRequestException();
}
@GetMapping("/test1/exception-2")
public String badRequestException2() {
throw new IllegalArgumentException();
}
}
----
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestController2 {
@GetMapping("/test2/exception-1")
public String badRequestException() {
throw new BadRequestException();
}
@GetMapping("/test2/exception-2")
public String badRequestException2() {
throw new IllegalArgumentException();
}
}
ControllerAdvice (총 3개의 클래스 코드)
// 에러 메시지 전달용 클래스
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class ErrorForm {
private String code;
private String message;
private String url;
}
----
// 모든 Controller에 적용
import hello.exception.exception.BadRequestException;
import javax.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@Slf4j
@RestControllerAdvice
public class TestGlobalControllerAdvice {
@ResponseStatus(HttpStatus.NOT_ACCEPTABLE)
@ExceptionHandler({IllegalArgumentException.class, BadRequestException.class})
public ErrorForm globalExceptionHandler(HttpServletRequest request, RuntimeException e) {
return new ErrorForm(HttpStatus.NOT_ACCEPTABLE.toString(), "접근 불가 - 글로벌 예외 처리", request.getRequestURI());
}
}
----
// TestController.class만 적용
import hello.exception.exception.BadRequestException;
import hello.exception.exception.TestController;
import javax.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@Slf4j
@RestControllerAdvice(assignableTypes = TestController.class)
public class TestControllerAdvice {
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(IllegalArgumentException.class)
public ErrorForm illegalArgumentExceptionHandler(HttpServletRequest request, RuntimeException e) {
return new ErrorForm(HttpStatus.BAD_REQUEST.toString(), "잘못된 인수 전달", request.getRequestURI());
}
@ExceptionHandler
public ResponseEntity<ErrorForm> badRequestExceptionHandler(HttpServletRequest request, BadRequestException e) {
ErrorForm errorForm = new ErrorForm(HttpStatus.I_AM_A_TEAPOT.toString(), "나는 주전자입니다. 테스트", request.getRequestURI());
return new ResponseEntity<>(errorForm, HttpStatus.I_AM_A_TEAPOT);
}
}
정리
ControllerAdvice를 사용하여 Controller에서 예외 처리 로직을 분리함과 동시에 특정 Controller에만 적용하거나 Global 적용이 가능하게 되었다. 또한 ExceptionHandler를 통해 손쉽게 예외에 따른 에러 메시지 전달도 가능해졌으며, ResponseStatus를 사용하여 HTTP 상태 코드도 쉽게 처리할 수 있게 되었다.
'Develop > TIL(Today I Learned)' 카테고리의 다른 글
Spring - 클래스 초기화 방법 비교 (PostConstruct, EventListener) (0) | 2023.03.06 |
---|---|
Spring Converter & Formatter (0) | 2023.02.24 |
Spring MVC - 예외 처리(Error page) (0) | 2023.02.20 |
Servlet Filter & Spring Interceptor (0) | 2023.02.20 |
JPA - 엔티티 수정(merge, dirty check)에 대한 내용 정리 (0) | 2023.02.15 |