Chained Exception

hongo·2024년 11월 5일
0

상황

Spring boot에서 WebClient를 사용해 다른 서버로 REST api 요청을 보낼 때, io.netty.channel.ConnectTimeoutException이 발생하면 별도로 처리하고 싶었다.

  • 예시코드
        webClient.get()
                .uri(...)
                .header(...)
                .exchangeToMono(...)
                .doOnError(error -> {
                    log("에러 발생 : " + error.getMessage());
                })
                .onErrorMap(error -> {
                    if (error instanceof io.netty.channel.ConnectTimeoutException) {
                        log("커넥션 타임 아웃 발생");
                        // custom 로직
                    }
                    return error; // 그 외의 예외는 그대로 반환
                })
                .block();

.doOnError의 log에서 io.netty.channel.ConnectTimeoutException이 발생하는 것을 보고, if문으로 error의 instanceof를 판단하여 ConnectTimeoutException들을 걸러낼 수 있을거라 생각했다.

그런데, 오이잉~?
if (error instanceof io.netty.channel.ConnectTimeoutException)에서 잡히지가 않았다?

log("에러 클래스: " + cause.getClass().getName()); 로 확인해보니 WebClientRequestException 클래스인 것을 볼 수 있었다.

Chained Exception

이유는 Chained Exception이기 때문이다.
예외들은 자신의 원인이 된 예외 클래스를 cause라는 필드로 가질 수 있다.

  • WebClientRequestException의 cause인 ConnectTimeoutException

그 동안 수많은 예외를 마주하면 보았던 저 Caused by:..........저게 다 예외 체이닝이었던 거임. 나만 이거 지금 알았냐 ????????

Exception들은 Throwable클래스를 상속받고 있는데, Throwable은 initCause와 getCause 메서드를 가지고 있다. 그렇기에 모든 예외 클래스들은 자신의 cause를 등록하고, 조회할 수 있다.

Chained Exception 왜 사용함?

extends RuntimeException 처럼 상속으로도 예외들간의 관계를 표현할 수 있다. 하지만 상속은 각 예외들의 종류를 구분하고 묶기 위한 용도가 크고, 예외 체이닝은 어떤 예외가 발생한 근본적인 예외를 명시하여 디버깅을 더 쉽게 하도록 돕는데 의의가 있는 듯 하다.

보통 checked exception을 unchecked exception(runtime exception)으로 바꿔 던지는 걸 지향하는데, 예외 체이닝을 사용하면 상속 관계를 만들지 않고도 간편하게 checked exception을 unchecked exception으로 감쌀 수 있다는 장점이 있다.

아 그래서 수정된 코드

 

// 수정 전 코드
.onErrorMap(error -> {
                    // 어림도 없지
                    if (error instanceof io.netty.channel.ConnectTimeoutException) { 
                        log("커넥션 타임 아웃 발생");
                        // custom 로직
                    }
                    return error; // 그 외의 예외는 그대로 반환
                })
// 수정1
.onErrorMap(error -> {
                    // cause를 뺌...
                    Throwable cause = error.getCause();
                  
                    if (cause instanceof io.netty.channel.ConnectTimeoutException) {
                        log("커넥션 타임 아웃 발생");
                        // custom 로직
                    }
                    return error; // 그 외의 예외는 그대로 반환
                })
// 수정2 while문으로 cause 돌리기 ... ...
.onErrorMap(error -> {
    Throwable cause = error;
    while (cause != null) {
        // ConnectTimeoutException인지 확인
        if (cause instanceof io.netty.channel.ConnectTimeoutException) {
            log("커넥션 타임 아웃 발생");
            // custom 로직
        }

        // 다음 원인으로 이동
        cause = cause.getCause();
    }

    // ConnectTimeoutException이 없으면 원래 예외 반환
    return error;
})
profile
https://github.com/hgo641

0개의 댓글