빙응의 공부 블로그

[Spring]예외 처리 전략 본문

Spring/개인공부_이론

[Spring]예외 처리 전략

빙응이 2024. 3. 29. 16:25

📝예외 처리 개요

일반적으로 비즈니스 로직만큼 예외처리에 투자하지 않는다고 한다. 그러나 무성의한 예외 처리는
애플리케이션의 많은 버그를 낳을 수 있기에 올바른 예외 처리 방법을 알아보자.

 

 

스프링의 오류 처리 방법은 아래 포스팅에서 한번 했었다.

[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 (tistory.com)

 

[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)