일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- SQLSTATE=42705
- dm_exec_requests
- 요약
- getChannel()
- XWPF
- spring
- TRANCOUNT
- 자바
- HWPF
- 프론트컨트롤러
- java
- 앵커멤버
- 함수
- 교육법
- 홈스쿨링
- 요청매핑
- XACT_STATE
- 아이
- ERROR_MESSAGE
- SQL
- MSSQL
- 튜닝
- 배치
- 디스패처서블릿
- git
- 진경혜
- 스프링
- 재귀멤버
- renameTo
- transferTo
- Today
- Total
필기노트
김영한 스프링 강의 요약 - API 예외 처리 본문
1. API 오류
API는 각 시스템 마다 응답의 모양도 다르고, 스펙도 모두 다르다. 예외 상황에 단순히 오류 화면을 보여주는 것이 아니라, 예외에 따라서 각각 다른 데이터를 출력해야 할 수도 있다. 그리고 같은 예외라고 해도 어떤 컨트롤러에서 발생했는가에 따라서 다른 예외 응답을 내려주어야 할 수 있다. 한마디로 매우 세밀한 제어가 필요하다.
결국 지금까지 살펴본 BasicErrorController 를 사용하거나 HandlerExceptionResolver 를 직접 구현하는 방식으로 API 예외를 다루기는 쉽지 않다.
스프링은 API 예외 처리 문제를 해결하기 위해 @ExceptionHandler 라는 애노테이션을 사용하는 매우 편리한 예외 처리 기능을 제공하는데, 실무에서 API 예외 처리는 대부분 이 기능을 사용한다.
ErrorResult (예외가 발생했을 때 API 응답으로 사용하는 객체를 정의했다.)
package hello.exception.exhandler;
@Data
@AllArgsConstructor
public class ErrorResult {
private String code;
private String message;
}
ApiExceptionV2Controller (정상 코드)
package hello.exception.exhandler;
@Slf4j
@RestController
public class ApiExceptionV2Controller {
@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);
}
@Data
@AllArgsConstructor
static class MemberDto {
private String memberId;
private String name;
}
}
ExControllerAdvice (예외처리 코드)
package hello.exception.exhandler.advice;
@Slf4j
@RestControllerAdvice
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());
}
@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);
}
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler
public ErrorResult exHandle(Exception e) {
log.error("[exceptionHandle] ex", e);
return new ErrorResult("EX", "내부 오류");
}
}
2. @ExceptionHandler 예외 처리 방법
1) @ExceptionHandler 애노테이션을 선언하고, 해당 컨트롤러에서 처리하고 싶은 예외를 지정해주면 된다. 해당 컨트롤러에서 예외가 발생하면 아래의 메서드가 호출된다.
2) 참고로 지정한 예외 또는 그 예외의 자식 클래스는 모두 잡을 수 있다.
3) 다양한 예외를 한번에 처리할 수 있다. (@ExceptionHandler({AException.class, BException.class}))
4) @ExceptionHandler 에 예외를 생략할 수 있다. 생략하면 메서드 파라미터의 예외가 지정된다.
5) @ExceptionHandler 에는 마치 스프링의 컨트롤러의 파라미터 응답처럼 다양한 파라미터와 응답을 지정할 수 있다.
3. @ControllerAdvice
1) @ControllerAdvice 는 대상으로 지정한 여러 컨트롤러에 @ExceptionHandler , @InitBinder 기능을 부여해주는 역할을 한다.
2) @ControllerAdvice 에 대상을 지정하지 않으면 모든 컨트롤러에 적용된다. (글로벌 적용)
3) @RestControllerAdvice 는 @ControllerAdvice 와 같고, @ResponseBody 가 추가되어 있다.
4) 대상 컨트롤러 지정 방법
// Target all Controllers annotated with @RestController
@ControllerAdvice(annotations = RestController.class)
public class ExampleAdvice1 {}
// Target all Controllers within specific packages
@ControllerAdvice("org.example.controllers")
public class ExampleAdvice2 {}
// Target all Controllers assignable to specific classes
@ControllerAdvice(assignableTypes = {ControllerInterface.class,
AbstractController.class})
public class ExampleAdvice3 {}
특정 애노테이션이 있는 컨트롤러를 지정할 수 있고, 특정 패키지를 직접 지정할 수도 있다. 패키지 지정의 경우 해당 패키지와 그 하위에 있는 컨트롤러가 대상이 된다. 그리고 특정 클래스를 지정할 수도 있다. 대상 컨트롤러 지정을 생략하면 모든 컨트롤러에 적용된다.
@ExceptionHandler 와 @ControllerAdvice 를 조합하면 예외를 깔끔하게 해결할 수 있다.
4. 실행결과
1) http://localhost:8080/api2/members/bad
IllegalArgumentException 처리
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(IllegalArgumentException.class)
public ErrorResult illegalExHandle(IllegalArgumentException e) {
log.error("[exceptionHandle] ex", e);
return new ErrorResult("BAD", e.getMessage());
}
실행 흐름
- 컨트롤러를 호출한 결과 IllegalArgumentException 예외가 컨트롤러 밖으로 던져진다.
- 예외가 발생했으로 ExceptionResolver 가 작동한다. 가장 우선순위가 높은 ExceptionHandlerExceptionResolver 가 실행된다.
- ExceptionHandlerExceptionResolver 는 해당 컨트롤러에 IllegalArgumentException 을 처리할 수 있는 @ExceptionHandler 가 있는지 확인한다.
- illegalExHandle() 를 실행한다. @RestController 이므로 illegalExHandle() 에도 @ResponseBody 가 적용된다. 따라서 HTTP 컨버터가 사용되고, 응답이 다음과 같은 JSON으로 반환된다.
- @ResponseStatus(HttpStatus.BAD_REQUEST) 를 지정했으므로 HTTP 상태 코드 400으로 응답한다.
결과
{
"code": "BAD",
"message": "잘못된 입력 값"
}
2) http://localhost:8080/api2/members/user-ex
UserException 처리
@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);
}
- @ExceptionHandler 에 예외를 지정하지 않으면 해당 메서드 파라미터 예외를 사용한다. 여기서는 UserException 을 사용한다.
- ResponseEntity 를 사용해서 HTTP 메시지 바디에 직접 응답한다. 물론 HTTP 컨버터가 사용된다. ResponseEntity 를 사용하면 HTTP 응답 코드를 프로그래밍해서 동적으로 변경할 수 있다. 앞서 살펴본 @ResponseStatus 는 애노테이션이므로 HTTP 응답 코드를 동적으로 변경할 수 없다.
3) http://localhost:8080/api2/members/ex Postman 실행
Exception
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler
public ErrorResult exHandle(Exception e) {
log.error("[exceptionHandle] ex", e);
return new ErrorResult("EX", "내부 오류");
}
- thrownewRuntimeException("잘못된 사용자")이코드가실행되면서,컨트롤러밖으로 RuntimeException 이 던져진다.
- RuntimeException 은 Exception 의 자식 클래스이다. 따라서 이 메서드가 호출된다.
- @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) 로 HTTP 상태 코드를 500으로 응답한다.
'김영한 강의 요약' 카테고리의 다른 글
김영한 스프링 강의 요약 - 커넥션풀과 데이터소스 이해 (0) | 2024.01.26 |
---|---|
김영한 스프링 강의 요약 - JDBC 이해 (1) | 2024.01.23 |
김영한 스프링 강의 요약 - 예외 처리와 오류 페이지 (0) | 2023.12.28 |
김영한 스프링 강의 요약 - 필터, 인터셉터 (0) | 2023.12.20 |
김영한 스프링 강의 요약 - 로그인(세션, 쿠키) (0) | 2023.12.15 |