스프링에서 예외 처리하는 3가지 방법
1. try - catch
“각 메소드” 안에서 처리
2. 어노테이션 @ExceptionHandler
-> 이 방법을 적용하여 설명 예정
“컨트롤러 자체”에서 처리
@controller, @RestController가 적용된 Bean 내에서 발생하는 예외를 잡아서 하나의 메서드에서 처리해주는 기능
Controller 내부에서 호출한 Service에서 예외가 발생 시 에러 처리 대상이 된다.
예외 처리 메시지를 예외 발생마다 새롭게 작성하면 번거롭고 복잡하다ㅜ
이젠 한 번에 처리해보자!
3. Global level에서 처리 → 추천
클라이언트에서 전달되기 직전에 처리
[ 실행흐름 ]
[컨트롤러에서 예외 발생]
- 에러가 터지면
- Dispatcher Servlet을 통해 ExceptionResolver 에게 간다.
- ExceptionHandlerExceptionResolver에게 간다.
- 이것이 컨트롤러에게 가서 “@ExceptionHandler”부분을 찾는다.
- 해당 메소드를 실행시킨다.
[자세한 version]
컨트롤러를 호출한 결과 IllegalArgumentException 예외가 컨트롤러 밖으로 던져진다.
예외가 발생했으로 ExceptionResolver 가 작동한다.
가장 우선순위가 높은 ExceptionHandlerExceptionResolver 가 실행된다.
ExceptionHandlerExceptionResolver 는 해당 컨트롤러에 IllegalArgumentException 을 처리할 수 있는 @ExceptionHandler 가 있는지 확인한다.
illegalExHandle() 를 실행한다. @RestController 이므로 illegalExHandle() 에도 @ResponseBody 가 적용된다.
따라서 HTTP 컨버터가 사용되고, 응답이 다음과 같은 JSON으로 반환된다. @ResponseStatus(HttpStatus.BAD_REQUEST) 를 지정했으므로 HTTP 상태 코드 400으로 응답한다.
Custom Exception (사용자 정의 예외)
프로젝트 상황에 맞춤형 예외를 만들 수 있다.
커스텀 예외의 이름만 봐도 어떤 예외인지 알아볼 수 있도록 하자.
표준 예외의 예시 : NullPointerException , IllegalArgumentException, IndexOutBoundsException 등
하지만 표준 예외를 사용하고 errorMessage 로 오류 상황을 나타내는 정도로도 충분히 표현이 가능하다면, 굳이 커스텀 예외를 사용하지 않는것이 좋다. 반대로 CustomException을 받는 곳에서 처리하기 쉽기 위해 추가로 에러에 관한 정보를 넣어주거나, 여러 타입의 Exception이 발생할 수 있는 코드에서 한 가지로 Exception 타입으로 묶어 처리할때는 custome exception을 사용할만 하다.
참고 : https://w97ww.tistory.com/74
직접 구현한 코드를 통해 구체적으로 알아보자
1) 파일의 구조를 살펴보자
먼저 global 폴더에 다음과 같이 3개의 파일들을 생성한다.
그리고 각 에러 타입마다 클래스 파일을 만든다. 여기에서는 UserNotFoundException에 대해서만 다룰 것이기 때문에 다음과 같이 Exception 파일(커스텀 Exception)을 만든다.
2) 각 파일의 내용을 살펴보자
[ ErrorCode ]
enum 타입으로 만들어서 에러 코드 메시지를 통일성있게, 재사용 가능하도록 구성했다.
@Getter
@RequiredArgsConstructor
public enum ErrorCode {
// Basic - C0***
RUNTIME_EXCEPTION(BAD_REQUEST, "C0001", "RUNTIME_EXCEPTION"),
// User - C2***
DUPLICATE_NICKNAME(CONFLICT, "C2001", "DUPLICATE_NICKNAME_EXISTS"),
DUPLICATE_EMAIL(CONFLICT, "C2002", "DUPLICATE_EMAIL_EXISTS"),
USER_NOT_FOUND(NOT_FOUND, "C2003", "USER_NOT_FOUND"),
PASSWORD_NOT_MATCH(BAD_REQUEST, "C2004", "PASSWORD_NOT_MATCH")
private final HttpStatus status;
private final String code;
private final String message;
}
[ErrorResponse]
에러 발생 시 어떤 내용들을 보여줄 것인지에 따라 다르게 구성할 수 있다.
필자는 다음의 에러 메시지 내용들(JSON 응답)을 보여줄 것을 고려하여 코드를 작성했다.
{
"status": "NOT_FOUND",
"code": "USER_NOT_FOUND",
"message": "해당하는 사용자를 찾을 수 없습니다.",
"date": "2022-08-01T14:48:14.150235"
}
이 에러 응답 클래스를 통해 다양한 에러 타입들을 통일성있게 표현할 수 있다.
@Getter
@ToString
@NoArgsConstructor
public class ErrorResponse {
private HttpStatus status;
private ErrorCode code;
private String message;
private LocalDateTime date = LocalDateTime.now();
@Builder
public ErrorResponse(HttpStatus status, ErrorCode code, String message) {
this.status = status;
this.code = code;
this.message = message;
}
}
[UserNotFoundException]
에러 타입마다 클래스를 만든다.
뒤에 나올 GlobalExceptionHandler에서 각 예외 타입마다 어떻게 핸들링 할 것인가(예외 보내는 양식)에 대한 메소드들이 따로 존재한다. (참고 : ResourceNotFoundException에 이미 message를 포함하는 생성자가 있다)
import org.springframework.data.rest.webmvc.ResourceNotFoundException;
import static com.example.jiyun.global.constant.ResponseConstant.NOTFOUND_USER;
public class UserNotFoundException extends ResourceNotFoundException {
public UserNotFoundException() {
super(NOTFOUND_USER);
}
@Override
public String getMessage() {
return super.getMessage();
}
}
[GlobalExceptionHandler]
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(UserNotFoundException.class)
protected final ResponseEntity<ErrorResponse> handleUserNotFoundException(UserNotFoundException e) {
final ErrorResponse response = ErrorResponse.builder()
.status(HttpStatus.NOT_FOUND)
.code(ErrorCode.USER_NOT_FOUND)
.message(e.getMessage())
.build();
return ResponseEntity.status(response.getStatus()).body(response);
}
....
}
[참고]
위의 UserNotFoundException에서 ResourceNotFoundException을 상속했다.
import org.springframework.data.rest.webmvc.ResourceNotFoundException;
이를 사용하기 위해서는 build.gradle에서 다음을 추가하고 리프레쉬해줘야 에러 표시가 사라진다.
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-rest'
}
'WEB > Spring' 카테고리의 다른 글
Lombok 라이브러리의 @RequiredArgsConstructor (2) | 2022.09.09 |
---|---|
의존관계 주입 4가지 방법과 생성자 주입 권장 이유 (0) | 2022.09.04 |
@ComponentScan의 탐색 위치와 대상 (1) | 2022.09.01 |
@ComponentScan과 @Autowired (2) | 2022.08.27 |
@Configuration의 싱글톤 보장 (0) | 2022.08.26 |