💻본 포스팅은 '스프링 MVC 2편 - 백엔드 웹 개발 활용 기술 - 김영한'님의 강의를 듣고 작성되었습니다.
목차
1. 기본 예외처리
2. 서블릿 예외처리
3. 스프링 예외처리
4. 정상 코드와 예외 처리 코드의 분리 - @ControllerAdvice
기본 예외처리
- HTML의 경우엔 오류 페이지만 있다면 대부분의 문제를 스프링 측에서 해결해준다. 그러나 API의 경우엔 이야기가 조금 달라지게 되는데 오류 화면을 보여주는 것으로 끝내는 문제가 아닌 각 오류 상황에 맞는 응답 스펙을 정해 JSON으로 데이터를 내려주어야 하기 때문에 API의 예외 처리는 조금 더 까다로운 문제이다.
- 또한 스프링에서 제공하는 BasicErrorController를 확장해서 JSON 오류 메시지를 변경할 수는 있지만 이는 다른 방법을 사용해 처리하는 것이 훨씬 편리하고 쉽기 때문에 스프링 예외처리에서 살펴보도록 하겠다.
- HTML 화면을 이용해 오류 페이지를 처리할 때는 'BasicErrorController'를 사용하도록 하고, API 처리 문제는 @ExceptionHandler를 사용하여 처리하도록 하자
서블릿 예외처리
예외, sendError( ) 발생 시 사용할 예외 페이지 경로 설정 클래스
package hello.exception;
import org.springframework.boot.web.server.ConfigurableWebServerFactory;
import org.springframework.boot.web.server.ErrorPage;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
//서블릿이 제공하는 예외 발생 시 해당 페이지 제공
//@Component
public class WebServerCustomizer implements WebServerFactoryCustomizer<ConfigurableWebServerFactory> {
@Override
public void customize(ConfigurableWebServerFactory factory) {
//RequestDispatcher 상수로 정의되어 있음
//HTTP 상태 코드
ErrorPage errorPage404 = new ErrorPage(HttpStatus.NOT_FOUND, "/error-page/404");
ErrorPage errorPage500 = new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/error-page/500");
//예외 발생 시
ErrorPage errorPageEx = new ErrorPage(RuntimeException.class,"/error-page/500");
factory.addErrorPages(errorPage404, errorPage500, errorPageEx);
}
}
- 서블릿 예외처리를 사용하기 위해서 WAS에 예외 전달, response.sendError()가 호출되면 위에 등록한 예외 페이지 경로가 호출되기 위한 클래스를 사용하자
API 예외 컨트롤러
package hello.exception.api;
import hello.exception.exception.BadRequestException;
import hello.exception.exception.UserException;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ResponseStatusException;
@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);
}
@Data
@AllArgsConstructor
static class MemberDto{
private String memberId;
private String name;
}
}
- 정상처리
- JSON 형식으로 데이터가 정상 반환 된다.
- 우리가 기대하는 것은 오류 발생 상황에도 JSON 형식으로 데이터를 반환하기를 바라는 것이다. (API의 오류 발생 상황)
- 오류 발생
- HTML이 반환된다.
- HTML 반환은 API의 경우에 기대하는 바가 아니다. 이 역시도 JSON 형식으로 반환되기를 기대한다.
API 응답을 위한 클래스
@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");
Map<String, Object> result = new HashMap<>();
Exception ex = (Exception) request.getAttribute(ERROR_EXCEPTION);
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));
}
스프링 예외처리
- 스프링 MVC는 컨트롤러(핸들러) 밖으로 예외가 던져진 경우 예외를 해결하고, 동작을 새로 정의할 수 있는 방법을 제공해준다.
- 컨트롤러 밖으로 던져진 예외를 해결, 동작 방식을 변경하고 싶다면 HandlerExceptionResolver를 사용하면 된다 (이는 줄여서 ExceptionResolver이라고 한다.)
ExceptionResolver 우선순위
스프링 부트가 기본 제공하는 ExceptionResolver 우선순위는 다음과 같다.
- ExceptionHandlerExceptionResolver
- @ExceptionHandler 을 처리한다.
- API 예외 처리는 대부분 이 기능으로 해결한다.
- ResponseStatusExceptionResolver
- HTTP 상태 코드를 지정해준다.
- @ExceptionHandler을 사용해 API 오류를 처리해주면 HTTP 상태 코드를 200으로 정상 반환해주게 되는데 이럴 경우에도 직접 상태 코드 변경을 위해 @ExceptionHandler와 같이 사용하거나 단독으로 사용하거나 한다.
- DefaultHandlerExceptionResolver(우선 순위가 가장 낮음)
- 스프링 내부의 기본 예외를 처리한다.
- 파라미터의 바인딩 시점에 타입이 맞지 않으면 발생하는 TypeMismatchException이 발생하는데 이 오류는 바로 잡지 않으면 서블릿 컨테이너까지 오류가 올라가 결과적으로 500 오류를 발생시킨다.
- 파라미터 바인딩의 문제는 대부분 클라이언트가 HTTP 요청 정보를 잘못 호출(잘못된 값을 입력하는 경우)해서 발생하는 문제이기 때문에 이런 경우 HTTP에서는 상태 코드를 400으로 사용하도록 했다.
- DefaultHandlerExceptionResolver는 위와 같이 파라미터 바인딩 문제가 발생하면 500 오류가 아닌 400오류로 변경해준다.
ExceptionResolver 활용
- 예외 상태 코드변환
- 상태 코드가 400인 오류를 500 등으로 바꿀 수 있다.
- 뷰 템플릿 처리
- ModelAndView에 값을 채워 예외에 따른 새로운 오류 화면 뷰 렌더링을 해서 고객에게 제공해준다.
- API 응답 처리
- HTTP 응답 바디에 직접 데이터를 넣어주는 것도 가능하다.
예외 발생 처리(2가지 방법)
- WAS까지 예외를 던져 다시 /error를 호출
- 중복 없이 컨트롤러 내부에서 예외 발생 처리 끝내기
ExceptionHandlerExceptionResolver을 사용해서 컨트롤러 내부에서 예외 발생 처리 끝내기
@Slf4j
@RestController
public class ApiExceptionV2Controller {
/*컨트롤러 내부에서 터진 예외를 WAS까지 가지고 가지 않아서 컨트롤러에서 처리를 하는데
* 이는 200 상태코드를 반환하게 한다. 그러나 이를 원하지 않고 http 상태 코드를 다른 것으로
* 처리하고 싶다면 이때는 @ResponseStatus로 상태코드를 지정해주면 된다.*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(IllegalArgumentException.class)
public ErrorResult illegalExHandler(IllegalArgumentException e){
log.error("[exceptionHandler] ex", e);
return new ErrorResult("BAD", e.getMessage());
}
//@ExceptionHandler에 예외를 생략하면 메서드 파라미터로 예외를 지정합니다.
@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);
}
}
- 이 경우엔 설정한 예외의 하위 자식 클래스까지 모두 처리가 가능하다.
- 우선순위는 자식 클래스가 더 자세히 기록한 것으로 보아 부모 클래스 예외와 자식 클래스 예외가 있으면 자식 클래스 예외로 처리한다.
- @ExceptionHandler에 예외를 생략하면 메서드 파라미터로 예외를 지정합니다.
정상 코드와 예외 처리 코드의 분리 - @ControllerAdvice
@ControllerAdvice
- 정상 코드
- 예외 처리 코드
- @ControllerAdvice 또는 @RestControllerAdvice 를 사용한다
- @RestControllerAdvice는 클래스 제일 상단부에 @RestController가 붙어있을 때 사용하며 둘의 기능은 동일하다.
'Back-End > Spring' 카테고리의 다른 글
[Spring DB][JDBC 이해] - JDBC 사용 (0) | 2022.06.22 |
---|---|
[Spring DB][JDBC 이해] - JDBC와 ORM SQL Mapper에 대해서 알아보자. (0) | 2022.06.21 |
[Spring MVC2][예외 처리와 오류 페이지] - 스프링이 지원하는 오류 페이지 처리 방식 (0) | 2022.05.30 |
[Spring MVC2][예외 처리와 오류 페이지] - 필터와 인터셉터 (0) | 2022.05.28 |
[Spring MVC2][예외 처리와 오류 페이지] - 서블릿 예외 처리를 사용해서 오류 화면을 제공해보자 (0) | 2022.05.28 |