Stay Hungry Stay Foolish

자바/자바 중급

[자바 중급] 10. 예외 처리 2

dev스카이 2024. 10. 22. 15:34

 

코드를 통해 오류 발생 시뮬레이션과, 예외 처리 흐름을 알아보자. 

사용자의 입력을 통해 오류를 발생시킨 후, 예외를 따로 처리하지 않고 밖으로 던진다.

 

오류 발생 시뮬레이션

 

1️⃣ 1. 어떤 종류의 오류가 발생했는지 담는 클래스

public class NetworkClientExceptionV2 extends Exception {
    private String errorCode;

    public NetworkClientExceptionV2(String errorCode, String message) {
        super(message); //어떤 오류가 발생했는지 담는다.
        this.errorCode = errorCode;
    }
    //에러 코드를 조회할 수 있게 추가
    public String getErrorCode() {
        return errorCode;
    }
}
  • extends Exception 체크 예외 사용
  • 생성자 - public NetworkClientExceptionV2(String errorCode, String message) { ... }
    • 어떤 종류의 오류가 발생했는지 구분하기 위해 예외 안에 오류 코드를 보관한다.
    • 오류 메시지 message 에는 어떤 오류가 발생했는지 설명을 담아둔다.
    • 오류 메시지는 Exception의 상위 클래스인 Throwable에서 기본으로 제공하는 기능을 사용한다. ( super() )
  • Getter - public String getErrorCode() { ... }
    • 에러 코드를 조회한다.

 

2️⃣ 2. 연결과 실패 메시지를 출력할 클래스

public class NetworkClientV2 {
    private final String address;
    public boolean connectError; 
    public boolean sendError; 

    public NetworkClientV2(String address) {
        this.address = address;
    }

    public void connect() throws NetworkClientExceptionV2 {
        if (connectError) {
            throw new NetworkClientExceptionV2("connectError", address + "서버 연결 실패");
        }
        //연결 성공
        System.out.println(address + " 서버 연결 성공");
    }
    public void send(String data) throws NetworkClientExceptionV2{
        if (sendError) {
            throw new NetworkClientExceptionV2("sendError", address + "서버에 데이터 전송 실패" + data);
        }
        //전송 성공
        System.out.println(address + " 서버에 데이터 전송: " + data);
    }
    public void disConnect() {
        //연결 성공
        System.out.println(address + " 서버 연결 해제");
    }
    public void initError(String data) {
        if (data.contains("error1")) {
            connectError = true;
        }
        if (data.contains("error2")) {
            sendError = true;
        }
    }
}
  • address, connectError, sendError 필드
    • address : 서버의 주소를 담는 필드이다. 어떤 서버에 연결된건지 출력할 수 있도록 한다.
    • connectError : 이 필드의 값이 true이면 연결에 실패하고 예외를 던진다.
    • sendError : 이 필드의 값이 true이면 데이터 전송에 실패하고 예외를 던진다.
  • connect() throws NetworkClientExceptionV2 { ... }
    • connectError 필드가 true이면 예외를 밖으로 던진다.
    • 에러가 없으면 주소와 함께 서버에 연결을 성공했다는 메시지를 출력한다.
  • send(String data) throws NetworkClientExceptionV2 { ... }
    • sendError 필드가 true이면 예외를 밖으로 던진다.
    • 에러가 없으면 주소와 함께 연결한 서버에 데이터를 전송한다는 메시지를 출력한다.
  • disconnect() { ... }
    • 주소와 함께 서버와 연결을 해제한다는 메시지를 출력한다.
  • initError(String data)  { ... }
    • 이 메서드를 통해서 connectError, sendError 필드의 값을 true로 설정한다.
    • 사용자 입력 값에 "error1"이 있으면 connectError 오류가 발생하고, "error2"가 있으면 sendError 오류가 발생한다.

 

3️⃣ 3. 사용자의 입력값을 통해 서버와 연결, 데이터 전송, 해제하는 기능이 있는 클래스

public class NetworkServiceV2_1 {
    public void sendMessage(String data) throws NetworkClientExceptionV2{
        String address = "http://example.com";
        NetworkClientV2 client = new NetworkClientV2(address);
        client.initError(data);

        client.connect();
        client.send(data);
        client.disConnect();
    }
}
  • sendMessage(String data) throws NetworkClientExceptionV2 { ... }
    • 사용자의 입력을 인자로 받고 주소와 함께 메시지를 출력할 클래스의 인스턴스를 생성한다.
    • initError() 를 호출해 사용자 입력을 저장한다.
    • 데이터를 전송하고, 서버와 연결과 해제를 할 수 있도록 한다.
    • 오류가 발생하면 예외를 밖으로 던진다.

 

4️⃣ 4. 사용자 입력을 받는 main 클래스

public class MainV2 {
    public static void main(String[] args) throws NetworkClientExceptionV2{
        NetworkServiceV2_1 networkService = new NetworkServiceV2_1();

        Scanner scanner = new Scanner(System.in);
        while (true) {
            System.out.print("전송할 문자: ");
            String input = scanner.nextLine();
            if (input.equals("exit")) {
                break;
            }
            networkService.sendMessage(input);
            System.out.println();
        }
        System.out.println("프로그램을 정상 종료합니다.");
    }
}

 

 

예외 처리 흐름

- 입력이 hello 일 때 (정상 작동)

 

 

- 입력이 error1 일 때 (에러 발생으로 트레이스 스택 작동)

 

 

1️⃣ 1. 사용자 입력 (Main 클래스)

NetworkServiceV2_1 networkService = new NetworkServiceV2_1();
String input = scanner.nextLine();
networkService.sendMessage(input);
  • scanner를 통해 입력 받은 데이터  'error1'  가 input에 저장된다.
  • sendMessage()에 'error1'을 전달하고 호출한다.

 

2️⃣ 2. sendMessage() 호출 (NetworkServiceV2_1 클래스)

client.initError(data);
  • 전달 받은 data = 'error1' 을 initError()에 전달하고 호출한다.

 

3️⃣ 3. initError() 호출 (NetworkClientV2 클래스)

if (data.contains("error1")) {
        connectError = true;
}
  • 전달받은 data중에 'error1'이 있으므로 connectError 필드를 true로 변경한다.

 

※ boolean contains(CharSequence s)

  • contains() 함수는 대상 문자열에 특정 문자열이 포함되어 있는지 확인하는 함수이다.
  • 대/소문자를 구분한다

 

4️⃣ 4. connect() 호출 (NetworkServiceV2_1 클래스 ➜ NetworkClientV2 클래스)

client.connect();
  • NetworkClientV2 클래스의 connect() 메서드가 호출된다.
public void connect() throws NetworkClientExceptionV2 {
    if (connectError) {
        throw new NetworkClientExceptionV2("connectError", address + "서버 연결 실패");
    }
}
  • 3번에서 connectError 필드가 true로 변경되었으므로 예외를 발생시킨다. 그래서 다음과 같은 코드가 출력된 것이다.

throw를 통해 예외가 발생
throws를 통해 예외가 밖으로 던져짐

 

 

5️⃣ 5. connect() 를 호출한 곳으로 돌아옴 (NetworkServiceV2_1 클래스)

public void sendMessage(String data) throws NetworkClientExceptionV2{ ... }
  • 예외가 터졌으니 호출한 곳으로 다시 돌아간다. 여기서도 예외를 잡지 않고, throws를 통해 예외를 밖으로 던진다.

 

 

6️⃣ 6. sendMessage()를 호출한 곳으로 돌아옴 (Main 클래스)

 public static void main(String[] args) throws NetworkClientExceptionV2{...}
  • 최종적으로 호출한 곳인 main으로 돌아갔지만, throws가 있으므로 예외를 밖으로 던지면서 프로그램이 종료된다.

 

 

 

📌 정리

이렇게 일부로 예외를 발생시켜 예외가 처리되거나 밖으로 던져지는 흐름을 알 수 있다. 스택 트레이스를 통해 어디서 예외가 발생했는지를 바로 파악할 수 있는 것 또한 알 수 있었다.

여기서는 예외를 잡지 않고, 즉 제대로 처리하지 않고 밖으로 던지기만 했다. try ~ catch 문을 이용해 예외를 잡아 정상 흐름으로 복구할 수 있다는 것도 기억해두자.

 

주의할 점

자원을 사용한 후 disconnect()를 호출해서 연결을 반드시 해제해야 하는데 위 코드에서는 제대로 작동하지 않는다. 여기서는 다루지 않을 거지만, 예외가 발생해도 disconnect()를 반드시 호출해서 서버와의 연결을 해제하고 자원을 반납해야 한다.

 

외부 연결과 같은 자바 외부의 자원은 자동으로 해제가 되지 않아서, 외부 자원을 사용한 후에는 연결을 해제해서 외부 자원을 반드시 반납해야 한다는 것도 알아두자.