빙응의 공부 블로그
[Spring]예외 처리 전략 본문

📝예외 처리 개요
일반적으로 비즈니스 로직만큼 예외처리에 투자하지 않는다고 한다. 그러나 무성의한 예외 처리는
애플리케이션의 많은 버그를 낳을 수 있기에 올바른 예외 처리 방법을 알아보자.
스프링의 오류 처리 방법은 아래 포스팅에서 한번 했었다.
[Spring]스프링 MVC 2편 - API 오류 처리 (tistory.com)
[Spring]스프링 MVC 2편 - API 오류 처리
📝API 오류 처리 API의 예외 처리는 다르게 처리해줘야 한다. HTML 페이지의 경우 지금까지 설명했던 것 처럼 4XX,5XX 같은 오류 페이지만 있으면 대부분의 문제를 해결할 수 있다. 그러나 API의 경우
quddnd.tistory.com
위의 포스팅은 여러가지 예외처리 방법을 설명하는 것으로 이번 포스팅에서는 주로 사용할 것이 대해
포스팅할 것이다.
📝시작하기 전 잘못된 예외처리 케이스
종종 개발자 코드에서 발견되는 잘못된 예외처리 케이스이다. 정말 치명적인편으로 안쓰도록 하자
🚩예외 블랙홀
- 예외 상황이 발생해도 이를 무시하고 블랙홀처럼 먹어버리는 케이스이다. 이 경우 아래 코드와 같이 IOExceiption이 발생해도 try/catch 블록을 빠져나가 다음 로직을 계속 수행하게 된다. 이는 비즈니스 로직이 스무스하게 흘러갔다는 착각을 가져올 수 있고, 버그를 야기할 수 있다.
- 즉, 예외에 대한 적절한 대응을 해주어야 한다.
try{
...
}catch(IOException e){
System.out.println(e);
e.printStackTrace();
}
🚩 무책임 throws
- catch 블록으로 라이브러리가 던지는 예외들을 매번 throws로 선언하기 귀찮아 상위 클래스인 Exception 클래스를 throws하는 케이스이다. 어느 부분에서 오류가 난지 모르기 때문에 매우 치명적이다.
//AS-IS
public void test() throws IOException, FileNotFoundException {
... //CheckedException을 던지는 라이브러리 코드 사용 중
}
//TO-BE → 예외 처리따위 세탁기에 돌려버리자!
public void test() throws Exception {
... //CheckedException을 던지는 라이브러리 코드 사용 중
}
📝예외의 종류 및 특징
- 예외를 어떻게 다뤄야할지 알아보기 전, 예외의 종류와 특징을 알아보자
- 자바의 예외는 크게 3가지 있다.
- Error
- Checked Excepion
- UnCheckedException
🚩Error
- 시스템에 비정상적인 상황 발생 시 사용된다. 자바 VM에서 발생하여 애플리케이션 코드에서 잡을 수 없다.
- 보통 자원 고갈, 가상 머신의 오류, 프로그램이 안정적으로 실행될 수 없는 상황에서 발생한다.
- 즉, Error에 대한 예외처리는 구현하지 않는다.(시스템이 치명적인 오류로 인해 이미 실행이 불가능하기 때문)
🚩Checked Exception
- Exception은 Error와 달리 애플리케이션 작업 중 예외상황이 발생한 경우이다.
- 여기서 Checked Exception은 RuntimeException을 상속받지 않은 클래스를 말한다.
- Checked Exception을 발생시키는 메서드를 사용할 경우 반드시 예외 처리 코드를 함께 작성해야 한다.
| 예외 | 기능 |
| FileNotFoundException | 파일을 열거나 읽을 때 파일이 존재하지 않는 경우 |
| IOException | 입출력 작업 중 발생하는 일반적인 예외 |
| ParseException | 텍스트를 파싱하는 동안 날짜, 시간 등의 형식이 잘못된 경우 |
| SQLException | 데이터베이스 관련 작업 예외 |
| ClassNotFoundException | 클래스 로드할 때 해당 클래스가 존재 안하는 경우 |
| InterruptedException | 스레드가 sleep, wait 등의 메서드에서 인터럽트 호출 |
| IIIegalAccessException | 클래스 접근 제어자로 인한 접근 불가 |
| InstantiationException | 클래스 이스턴스를 생성할 수 없는 경우 |
| NoSuchMethodException | 메서드를 호출할 대 해당 메서드가 존재안하는 경우 |
String filePath = "example.txt";
try {
readFile(filePath);
} catch (FileNotFoundException e) {
System.err.println("파일을 찾을 수 없습니다: " + e.getMessage());
} catch (IOException e) {
System.err.println("입출력 오류가 발생했습니다: " + e.getMessage());
}
위 코드는 버퍼스트림 + 파일 입출력으로 파일은 FileNotFoundException이 발생할 수 있고
버퍼스트림은 IOException이 발생할 수 있다.
🚩Unchecked Exception
- RuntimeException 클래스를 상속하는 예외들은 예외처리를 강제하기 않기 때문에 Unchecked Exception이라 한다.
- 스프링에서 주로 생각해야 하는 예외처리가 이 UncheckedException이다.
| 예외 | 기능 |
| NullPointerException | 객체 참조가 없는 상태에서 인스턴스 맴버를 참조할 때 발생 |
| ArrayIndexOutOfBoundsException | 배열 범위를 벗어난 인덱스 사용 |
| ClassCastException | 부적절한 형변환 |
| NumberFormatException | 숫자 형식의 문자열을 숫자로 변환할 때, 오류 |
| IllegalArgumentException | 메서드에 잘못된 인수 전달 |
| IllegalStateException | 객체의 상태가 메서드 호출에 부적절 |
| UnsupportedOperationException | 지원되지 않는 작업 수행 |
- 사실 이외에도 각각의 비즈니스에 맞는 예외를 직접 커스텀해줘야 한다.
- 또한 오류 발생 시 DB 트랜잭션에 대한 처리도 해줘야 한다. 오류로 롤백할 지 롤백하지 않고 따로 처리할 지를 선택하는 것이다.
📝Optional - NullPointerException
- NullPointerException은 스프링에서 많이 처리하는 오류 중 하나이다.
String value = getValue();
if (value != null) {
// 값이 null이 아닌 경우에만 처리
}
- 해당 방식처럼 null 값을 확인해 처리할 수 있어 쉽게 처리 가능하지만 더 좋은 방법이 있다.
🚩Optional
- java8에서 등장한 옵셔널은 null 값을 안전하게 처리할 수 있게 도와준다.
- 자세한 설명은 아래 포스팅을 확인하자
[JAVA] Optional
📝 JAVA 8 Optional 자바 옵셔널은 자바 8에서 최초로 도입 되었다. 그 이유는 바로 NULL 때문이다. 프로그래밍을 하다보면 NULL 처리를 필수적으로 하게 된다. 기존에는 NULL 체크를 해서 NULL이 아닌 경
quddnd.tistory.com
📝Spring Api 예외 처리 - @ControllerAdvice
Spring은 전역적으로 예외처리를 할 수 있게 지원한다.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ControllerAdvice
@ResponseBody
public @interface RestControllerAdvice {
...
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface ControllerAdvice {
...
}
🚩@ControllerAdvice
- 여러 컨트롤러에 대해 전역적으로 ExceptionHandler를 적용하는 어노테이션이다.
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(DuplicateUserIdException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ResponseBody
public String handleDuplicateUserIdException(DuplicateUserIdException ex) {
return ex.getMessage();
}
}
@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", "내부 오류");
}
}
이렇게 여러가지 오류를 모아놓고 한번에 처리할 수 있게 도와준다.
ControllerAdvice의 이점은 다음과 같다.
- 하나의 클래스로 모든 컨트롤러에 대한 전역적 예외처리
- 직접 정의한 에러 응답을 일관성있게 클라이언트에 내려줄 수 있다.
- 별도의 try-catch문이 없어 코드의 가독성을 높아짐
📝Spring Rest Api 예외처리 - @RestControllerAdivce
RestFull한 예외처리를 해보자
1. 에러 코드 정의하기
- 먼저 클라이언트에 보내줄 에러 코드를 정의하자, 자신만의 특별한 형식을 만드는 것이다.
@Getter
@RequiredArgsConstructor
public enum CommonErrorCode implements ErrorCode {
INVALID_PARAMETER(HttpStatus.BAD_REQUEST, "Invalid parameter included"),
RESOURCE_NOT_FOUND(HttpStatus.NOT_FOUND, "Resource not exists"),
INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "Internal server error"),
;
private final HttpStatus httpStatus;
private final String message;
}
@Getter
@RequiredArgsConstructor
public enum UserErrorCode implements ErrorCode {
INACTIVE_USER(HttpStatus.FORBIDDEN, "User is inactive"),
;
private final HttpStatus httpStatus;
private final String message;
}
2. 사용자 정의 예외 클래스 정의
public class CustomException extends RuntimeException {
private final ErrorCode errorCode;
public CustomException(ErrorCode errorCode) {
super(errorCode.getMessage());
this.errorCode = errorCode;
}
public ErrorCode getErrorCode() {
return errorCode;
}
}
3. 에러 응답 형식 만들기 및 컨트롤러 적용
public class ErrorResponse {
private String errorCode;
private String errorMessage;
public ErrorResponse(String errorCode, String errorMessage) {
this.errorCode = errorCode;
this.errorMessage = errorMessage;
}
// Getter 및 Setter 메서드
}
@RestController
public class UserController {
@Autowired
private UserService userService;
@PostMapping("/users")
public ResponseEntity<Object> createUser(@RequestBody User user) {
try {
userService.createUser(user);
return ResponseEntity.ok().build();
} catch (CustomException ex) {
ErrorResponse errorResponse = new ErrorResponse(ex.getErrorCode());
return ResponseEntity.status(ex.getErrorCode().getHttpStatus()).body(errorResponse);
}
}
}
이렇게하면 JSON 형식으로 오류를 반환할 수 있다.
✔참고 사이트
[토비의 스프링 스터디] 7주차 / 예외 / 예외 처리 방법 및 전략 :: 영암사는 승경이네 (tistory.com)
[토비의 스프링 스터디] 7주차 / 예외 / 예외 처리 방법 및 전략
1. 개요 일반적으로 비지니스 로직 개발만큼 예외처리에 투자하지 않는다. 무성의한 예외처리는 어플리케이션의 많은 버그를 낳을 수 있다. 올바른 예외처리 방법을 알아보자. 2. 초난감 예외처
tlatmsrud.tistory.com
[Spring] @RestControllerAdvice를 이용한 Spring 예외 처리 방법 - (2/2) - MangKyu's Diary (tistory.com)
[Spring] @RestControllerAdvice를 이용한 Spring 예외 처리 방법 - (2/2)
예외 처리는 robust한 애플리케이션을 만드는데 매우 중요한 부분을 차지한다. Spring 프레임워크는 매우 다양한 에러 처리 방법을 제공하는데, 앞선 포스팅에서 @RestControllerAdvice를 사용해야 하는
mangkyu.tistory.com
자바 공부를 어떻게 하길래, "언체크드 예외 발생시 트랜잭션 롤백?" (youtube.com)
'Spring > 개인공부_이론' 카테고리의 다른 글
| [Spring]단위 테스트 정리하기 JUnit(1/3) (1) | 2024.10.18 |
|---|---|
| [Spring API]Spring WebFlux와 WebClient (0) | 2024.05.24 |
| JWT 개요 (1) | 2024.03.24 |
| [Spring]스프링 시큐리티 - 구조 이해하기 (0) | 2024.03.22 |
| [Spring]스프링 시큐리티 - 개요 (0) | 2024.03.21 |