본 게시물은 스스로의 공부를 위한 글입니다.
틀린 내용이 있을 수 있습니다.
HTML 오류 페이지야 그냥 고객 친화적으로 이쁘게 만들어서 보여주면 되지만, API오류는 다른 차원의 이야기이다.
예를 들어 상품과 관련된 API에서 발생하는 오류와 로그인 관련된 API에서 발생하는 오류 처리를 다르게할 필요가 있을 수 있다.
주로 클라이언트와 API 오류 스팩을 정해놓고 이에 맞춘다.
httpStatus=404으로 전송.
{
"code"=2341,
"message"=상품 페이지 요청 오류입니다.
}
스프링 MVC는 컨트롤러 밖으로 예외가 던져진 경우 예외를 해결하고, 동작을 새로 정의할 수 있는 방법을 제공한다.
바로 HandlerExceptionResolver
을 사용하면된다.
우리는 컨트롤러에서부터 날라오는 예외를 WAS까지 안보내고, HandlerExceptionResolver
에서 해결한 후, 정상적인 호출로 WAS로 리턴할거다.
스프링 부트가 기본으로 제공하는 ExceptionResolver
는 다음과 같다.
ExceptionHandlerExceptionResolver
ResponseStatusExceptionResolver
-> HTTP 상태 코드를 지정해준다.
DefaultHandlerExceptionResolver
->스프링 내부 기본 예외를 처리한다.
실무에서는 거의 무조건 ExceptionHandlerExceptionResolver
을 사용하니, 여기에선 이것만 알아보겠다.
ExceptionHandlerExceptionResolver
를 @ExceptionHandler
라는 애노테이션으로 기능을 제공한다.@Data
@AllArgsConstructor
public class ErrorResult {
private String code;
private String message;
}
@RestController
public class ApiExceptionController {
@GetMapping("/api/{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("사용자 오류");
}
}
}
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(IllegalArgumentException.class)
public ErrorResult illegalExHandle(IllegalArgumentException e) {
log.error("[exceptionHandle] ex", e);
return new ErrorResult("BAD", e.getMessage());
}
RestController
에 이 코드를 추가하면 된다.@ResponseStatus
을 이용해 HttpStatus를 지정할 수 있다.@ExceptionHandler
에서 IllegalArgumentException.class
를 캐치하는걸 확인 할 수 있다. 그럼 이 컨트롤러에서 터진 IllegalArgumentException
예외는 이 메소드가 잡게되고, 정상적인 흐름으로 return 되는걸 볼 수 있다.return new ErrorResult("BAD", e.getMessage());
을 통해 우리가 위에서 만든 오류 스팩에 맞추어 응답할 수 있다.@ExceptionHandler
public ResponseEntity<ErrorResult> userExHandle(UserException e) {
log.error("[exceptionHandle] ex", e);
ErrorResult errorResult = new ErrorResult("USER-EX", e.getMessage());
return new ResponseEntity<>(errorResult, HttpStatus.BAD_REQUEST);
}
1번
코드와 다른점@ExceptionHandler
에 예외를 생략할 수 있다. 생략한 경우 해당 메소드의 파라미터로 받은 UserException
예외를 잡게된다.ResponseEntity<>
로 응답을 보낼 수 있다.@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler
public ErrorResult exHandle(Exception e) {
log.error("[exceptionHandle] ex", e);
return new ErrorResult("EX", "내부 오류");
}
Q) 그럼
IllegalArgumentException
가 발생할 경우 1번 코드와 3번 코드가 동시에 실행되는건가요?
- A) 스프링의 우선순위는 항상 자세한 것이 가져갑니다. 그러므로 1번 코드가 예외를 잡게됩니다.
@ExceptionHandler
은 지정한 예외 말고도 그 하위 자식 클래스를 모두 처리할 수 있다.Exception
은 모든 예외를 잡을 수 있다.@ExceptionHandler
가 있다면 그것만 호출한다.@ExceptionHandler
가 발생한 예외를 잡을 수 없다면, 다른 ExceptionResolver
가 호출되게 되고(예를 들어 ResponseStatusExceptionResolver
등), 그래도 해결이 안되면 결국 WAS까지 예외가 내려오게 된다.@ControllerAdvice
을 사용하자.@Slf4j
@RestControllerAdvice //@ControllerAdvice + @ResponseBody
public class ExControllerAdvice {
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(IllegalArgumentException.class)
public ErrorResult illegalExHandle(IllegalArgumentException e) {
log.error("[exceptionHandle] ex", e);
return new ErrorResult("BAD", e.getMessage());
}
}
위처럼 예외 처리 코드는 다른 클래스로 분리하고, 컨트롤러에는 정상 처리 코드만 남겨두자.
@ControllerAdvice
은 대상 컨트롤러를 지정할 수 있다.
@ControllerAdvice
: 글로벌 적용@ControllerAdvice("org.example.controllers")
: 특정 패키지에 적용. 해당 패키지와 그 하위 컨트롤러 모두 대상이 된다.@ControllerAdvice(annotations = RestController.class)
: 특정 애노테이션이 있는 컨트롤러에 지정@ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class})
: 특정 클래스를 지정.보통 패키지 지정 정도를 사용한다.
@ExceptionHandler
와@ControllerAdvice
를 조합하면 예외를 깔끔하게 해결할 수 있다.
인프런의 '스프링 MVC 2편(김영한)'을 스스로 정리한 글입니다.
자세한 내용은 해당 강의를 참고해주세요