본문 바로가기
Java/Spring Basic

Exception

by o3oppp 2024. 11. 27.
예외 계층

예외 계층(ref.인프런)

  • Object : 예외도 객체. 모든 객체의 최상위 부모는 Object이므로 예외의 최상위 부모도 Object
  • Throwable : 최상위 예외
  • Error : 메모리 부족이나 심각한 시스템 오류와 같이 애플리케이션에서 복구 불가능한 시스템 예외
    • 개발자는 이 예외를 잡으려고 해서는 안됨
    • 상위 예외를 catch로 잡으면 그 하위 예외까지 함께 잡음
    • 애플리케이션 로직에서는 Throwable 예외를 잡으면 Error 예외도 함께 잡을 수 있기 때문에 해당 예외를 잡으면 안됨
    • 언체크 예외
  • Exception : 체크 예외
    • 애플리케이션 로직에서 사용할 수 있는 실질적인 최상위 예외
    • Exception과 그 하위 예외는 모두 컴파일러가 체크하는 체크 예외
    • 단 RuntimeException은 예외
  • RuntimeException : 언체크 예외 = 런타임 예외
    • 컴파일러가 체크하지 않는 언체크 예외
    • 그 자식 예외 또한 모두 언체크 예외
    • 런타임 예외라고도 함

예외 기본 규칙

예외 규칙(ref.인프런)

  1. 예외는 잡아서 처리하거나 던져야 함
  2. 예외를 잡거나 던질 때 지정한 예외뿐만 아니라 그 예외의 자식들도 함께 처리됨
    •     Exception을 catch로 잡으면 그 하위 예외들도 모두 잡을 수 있음
    •     Exception을 throws로 던지면 그 하위 예외들도 모두 던질 수 있음

예외를 처리하지 못하고 계속 던지는 경우

  • 자바 main() 쓰레드의 경우 예외 로그를 출력하면서 시스템이 종료됨
  • 웹 애플리케이션의 경우 WAS가 해당 예외를 받아서 처리하는데, 주로 개발자가 지정한 오류 페이지를 보여줌

체크 예외(Exception과 그 하위 예외)
public class CheckedTest {

   	...
    
    static class Controller{
        Service service = new Service();

        public void request() throws SQLException, ConnectException {
            service.logic();
        }
    }
    static class Service{
        Repository repository = new Repository();
        NetworkClient networkClient = new NetworkClient();

        public void logic() throws SQLException, ConnectException {
            repository.call();
            networkClient.call();
        }
    }

    static class NetworkClient{
        public void call() throws ConnectException {
            throw new ConnectException("연결 실패");
        }
    }

    static class Repository{
        public void call() throws SQLException {
            throw new SQLException("예외 발생");
        }
    }
}
  • Exception과 그 하위 예외(RuntimeException 제외)는 모두 컴파일러가 체크하는 체크 예외
  • 체크 예외는 잡아서 처리하거나 밖으로 던지도록 선언 필수. 그렇지 않으면 컴파일 오류 발생

장단점

  • 장점 : 개발자가 실수로 예외를 누락하지 않을 수 있음
  • 단점 : 모든 체크 예외를 반드시 잡거나 던지도록 해야 하기 때문에 catch, throw 등 선언 필수

체크 예외 문제점

1. 복구 불가능한 예외

  • SQLException을 예를 들면 문법 또는 DB 자체에 문제가 발생(서버 다운 등)하여 대부분 복구가 불가능
  • 특히나 서비스나 컨트롤러는 이런 문제를 해결할 수 없음
  • 따라서 오류를 빠르게 인지하여 서블릿 필터, 스프링 인터셉터, 스프링의 ControllerAdvice를 사용하여 공통으로 해결해야 함

2. 의존 관계에 대한 문제

  • 체크 예외이기 때문에 컨트롤러나 서비스 입장에서는 본인이 처리할 수 없어도 어쩔 수 없이  예외를 던져야 함
  • 예외를 던지는 부분을 코드에 선언하게 되면 서비스나 컨트롤러에서 java.sql.SQLException을 의존하게 됨
  • 향후 기술 변경 시 SQLException에 의존하던 모든 서비스, 컨트롤러의 코드를 JPAException에 의존하도록 고쳐야 함

기술 변경시 파급 효과(ref.인프런)


언체크 예외(RuntimeException과 그 하위 예외)
public class UncheckedAppTest {

    ...

    static class Controller{
        Service service = new Service();

        public void request() {
            service.logic();
        }
    }
    static class Service{
        Repository repository = new Repository();
        NetworkClient networkClient = new NetworkClient();

        public void logic() {
            repository.call();
            networkClient.call();
        }
    }

    static class NetworkClient{
        public void call() {
            throw new RuntimeConnectException("연결 실패");
        }
    }

    static class Repository{
        public void call() {
            try {
                runSQL();
            } catch (SQLException e) {
                throw new RuntimeSQLException(e);
            }
        }

        public void runSQL() throws SQLException {
            throw new SQLException("예외 발생");
        }
    }

    static class RuntimeConnectException extends RuntimeException{
        public RuntimeConnectException(String message) {
            super(message);
        }
    }

    static class RuntimeSQLException extends RuntimeException{
        public RuntimeSQLException(Throwable cause) {
            super(cause);
        }
    }
}
  • RuntimeException과 그 하위 예외는 컴파일러가 체크하지 않은 언체크 예외
  • 예외를 던지는 throws를 선언하지 않고 생략할 시 자동으로 예외를 던져줌

장단점

  • 장점 : 체크하고 싶지 않은 언체크 예외 무시 가능. 즉, throws 미선언 가능
  • 단점 : 컴파일러가 체크해주지 않기 때문에 개발자가 예외를 누락할 수 있음

언체크 예외 활용
@Slf4j
public class UncheckedAppTest {

    @Test
    void Unchecked(){
        Controller controller = new Controller();
        Assertions.assertThatThrownBy(() -> controller.request())
                .isInstanceOf(Exception.class);
    }

    @Test
    void printEx(){
        Controller controller = new Controller();
        try {
            controller.request();
        } catch(Exception e){
            log.info("ex", e);
        }
    }

    static class Controller{
        Service service = new Service();

        public void request() {
            service.logic();
        }
    }
    static class Service{
        Repository repository = new Repository();
        NetworkClient networkClient = new NetworkClient();

        public void logic() {
            repository.call();
            networkClient.call();
        }
    }

    static class NetworkClient{
        public void call() {
            throw new RuntimeConnectException("연결 실패");
        }
    }

    static class Repository{
        public void call() {
            try {
                runSQL();
            } catch (SQLException e) {
            	// 체크 예외를 언체크 예외로 전환
                throw new RuntimeSQLException(e);
            }
        }

        public void runSQL() throws SQLException {
            throw new SQLException("ex");
        }
    }

    static class RuntimeConnectException extends RuntimeException{
        public RuntimeConnectException(String message) {
            super(message);
        }
    }

    static class RuntimeSQLException extends RuntimeException{
        public RuntimeSQLException(Throwable cause) {
            super(cause);
        }
    }
}
  • 중간에 기술이 변경되어도 해당 예외를 사용하지 않는 컨트롤러, 서비스에서는 코드를 변경하지 않아도 됨
  • 구현 기술이 변경되는 경우, 예외를 공통으로 처리하는 곳에서 예외에 따른 다른 처리가 필요할 수 있음
  • 하지만 공통으로 처리하는 곳만 변경하면 되기 때문에 변경의 영향 범위는 최소화
  • throws 생략 가능

'Java > Spring Basic' 카테고리의 다른 글

프로토타입 스코프  (0) 2024.09.01
빈 생명주기 콜백  (1) 2024.09.01
의존관계 자동 주입  (0) 2024.08.29
@ComponentScan  (0) 2024.08.21
@Configuration  (0) 2024.08.20