Spring 기초/Spring 기초 지식

[Spring] RuntimeException의 메시지

Stark97 2023. 12. 21. 21:06
반응형
 
 

이번 포스트에서는 RuntimeException의 예외 메시지를 가져오는 getMessage()에 대해서 자세히 알아보자

📌 서론

테스트 코드를 작성하다 발생했던 문제를 해결하며 이 내용을 정리했다.

예외 처리 메시지를 가져오는 과정에서 문제가 있었기에 RuntimeException의 예외 메시지를 가져오는 과정을 정리해봤고 그 내용을 정리했다.

 

1. 테스트 코드에서 발생한 문제 파악하기

테스트 코드를 작성했다.

  • 테스트 코드를 작성하고 실행했는데 코드 중간에 작성된 assertEquals(expectedMessage, exception.getMessage())에서 오류가 발생했다.

테스트 코드
테스트 코드

 

테스트 코드 호출 결과

  • 테스트 결과를 보면 기대값이 존재하는데 Actual이 null인 오류가 발생했다고 나온다.

 

 

오류 분석 - [커스텀 예외처리 클래스를 확인]

  • 오류를 분석해 봤더니 내가 생성한 커스텀 예외 클래스인 RecipeApplicationException에서 예외 메시지가 null로 설정되어 있는 것 같다고 나왔다. 그래서 클래스 내부를 살펴봤다. 클래스 내부에는 필드로 ErrorCode가 선언되어 있었고 @AllArgsConstructor를 사용하고 있었는데 이게 문제였다.

  • @AllArgsConstructor는 모든 필드에 대한 값을 인자로 받는 생성자를 자동으로 생성한다. 이 경우, RecipeApplicationException의 생성자는 단순히 errorCode 필드를 초기화하지만, 상위 클래스인 RuntimeException의 message 필드는 설정되지 않는다. 따라서 exception.getMessage()를 호출하면 null을 반환하게 된다.
@Getter
@AllArgsConstructor
public class RecipeApplicationException extends RuntimeException {

	private ErrorCode errorCode;
	
}

 

 

2. 코드 수정 후 이전과 비교하기

RecipeApplicationException 클래스 수정하기

  • 기존에 @AllArgsConstructor로 생성자를 만들던 것을 직접 생성자를 선언하는 방식으로 코드를 수정했다.
  • 새롭게 작성한 코드에서는 super(errorCode.getMessage());를 호출하여 RuntimeException의 message 필드를 초기화한다. 이렇게 하면 RuntimeException의 생성자가 예외 메시지를 설정하게 된다. 결과적으로 exception.getMessage()를 호출하면 errorCode에 설정된 메시지를 반환한다.
@Getter
public class RecipeApplicationException extends RuntimeException {

	private ErrorCode errorCode;

	public RecipeApplicationException(ErrorCode errorCode) {
		super(errorCode.getMessage()); // 여기에서 예외 메시지를 설정합니다.
		this.errorCode = errorCode;
	}

}
  • 수정 이후 테스트 코드를 호출했더니 테스트가 성공했다.

 

 

차이점이 뭘까?

  • 수정 이전과 수정 후 코드의 차이는 RuntimeException의 message 필드가 설정되는 방식에 있다. 기본 생성자는 이 message 필드를 설정하지 않기 때문에, 예외 메시지가 없다. 반면 사용자 정의 생성자는 super()를 통해 이 필드를 명시적으로 설정하므로, 예외 메시지가 존재한다.

 

 

 

근데 RecipeApplicationException 코드를 수정할 필요가 있을까? 그냥 테스트 코드에서 exception.getErrorCode().getMessage()를 호출하면 되는 거 아닌가?

 

 

 

 

사실 이전에 작성한 @AllArgsConstructor를 사용하는 코드를 사용하면서 테스트 코드 내부에서  exception.getErrorCode().getMessage() 이렇게 호출하면 예외 메시지를 문제없이 잘 가져온다. 그러나 어쩌다 만나게 된 이 오류상황을 제대로 이해하고 싶어서 예외 메시지를 가져오는 동작을 더 자세히 알아봤다.

 

3. 동작 설명

RecipeApplicationException 클래스에서 super(errorCode.getMessage())를 사용하면, 이 클래스의 인스턴스가 생성될 때 RuntimeException의 message 필드가 자동으로 초기화된다. 

 

예시코드를 통해 알아보자

  • RecipeApplicationException클래스는 errorCode 필드만 명시적으로 가지고 있지만, RuntimeException의 message 필드를 통해 예외 메시지에 바로 접근할 수 있다. 이 방식은 Java의 표준 예외 처리와의 호환성을 높여준다.
public class RecipeApplicationException extends RuntimeException {
    private ErrorCode errorCode;

    public RecipeApplicationException(ErrorCode errorCode) {
        super(errorCode.getMessage()); // RuntimeException의 message 필드 초기화
        this.errorCode = errorCode;
    }
}

// 예외 발생 시
RecipeApplicationException exception = new RecipeApplicationException(ErrorCode.SOME_ERROR);
System.out.println(exception.getMessage()); // "SOME_ERROR"의 메시지 출력

 

Java의 RuntimeException 이해하기

  • RuntimeException은 Java에서 예외 처리의 근간을 이루는 클래스다. 이 클래스는 모든 unchecked 예외의 부모 클래스로, 일반적인 프로그래밍 오류를 나타내는 예외들을 포함한다. RuntimeException 클래스에는 message라는 필드가 있는데, 이 필드는 발생한 예외에 대한 설명이나 정보를 저장하는 데 사용된다. 이 메시지는 예외가 발생했을 때 유용한 정보를 제공하여, 오류의 원인을 파악하는 데 도움을 준다.

  • 자식 클래스에서 super()를 호출하면, 해당 클래스의 부모 클래스의 생성자가 실행된다. 예를 들어, RuntimeException을 상속받는 자식 클래스에서 super(errorCode.getMessage())를 호출하면, RuntimeException 클래스의 생성자가 호출되며, 이때 예외 메시지로 errorCode.getMessage()의 값을 전달하여 RuntimeException의 message 필드를 초기화한다.

 

커스텀 예외 RecipeApplicationException의 동작 방식

  • RecipeApplicationException 클래스는 사용자 정의 예외 클래스로, RuntimeException을 상속받는다. 이 클래스의 생성자에서 super(errorCode.getMessage())를 호출하면, RuntimeException의 message 필드가 errorCode.getMessage()의 값으로 설정된다. 이 과정은 예외 객체가 생성될 때 자동으로 수행된다.

  • RecipeApplicationException 인스턴스에서 .getMessage() 메서드를 호출하면, 내부적으로 RuntimeException의 message 필드에 저장된 값이 반환된다. 따라서, 이 메서드를 통해 errorCode.getMessage()에 설정된 메시지를 직접 얻을 수 있게 된다. 이렇게 하면 예외 처리 시, 예외 객체를 통해 직접적으로 오류 메시지를 접근하고 확인할 수 있어, 오류 처리와 디버깅이 더욱 용이해진다.

 

 

근데 내용을 읽다보면 계속 RuntimeExceptioin에 message 필드가 있다고 하는데 이게 뭘까?



4. RuntimeException의 message 필드 설명

RuntimeException의 message 필드는 Throwable 클래스에 선언된 detailMessage 필드와 동일하다. RuntimeException은 Throwable 클래스를 상속받기 때문에, Throwable의 모든 필드와 메서드를 사용할 수 있다.

 

Throwable 클래스 이해하기

  • Throwable 클래스는Java의 모든 오류와 예외의 최상위 클래스다. Throwable 클래스에는 예외나 오류에 대한 정보를 담는 필드들이 존재한다. 이 안에 detailMessage 필드가 존재하는데 이 필드는 예외나 오류의 구체적인 설명을 저장한다. 일반적으로 예외 메시지를 저장하는 데 사용된다.
public class Throwable implements Serializable {

    @java.io.Serial	
    private static final long serialVersionUID = -3042686055658047285L;

    private transient Object backtrace;
    
    private String detailMessage;
    
}

RuntimeException과 Throwable의 관계

  • RuntimeException은 Exception을 상속받고, Exception은 Throwable을 상속받는다. 따라서 RuntimeException은 Throwable의 모든 속성과 기능을 상속받는다.

runtimeException의 상속관계
runtimeException의 상속관계

  • 만약 RuntimeException 또는 그 하위 클래스의 인스턴스에서 .getMessage() 메서드를 호출하면, Throwable 클래스의 detailMessage 필드 값을 반환한다.

throwable에 존재하는 getMessage 메서드
throwable에 존재하는 getMessage 메서드

예시코드

  • 이 예시에서 MyException 클래스는 RuntimeException을 상속받으며, 생성자에서 super(message)를 호출함으로써 Throwable의 detailMessage 필드를 초기화한다. 그 결과, .getMessage() 메서드로 이 메시지에 접근할 수 있다.
public class MyException extends RuntimeException {
    public MyException(String message) {
        super(message); // 이 호출은 Throwable의 detailMessage를 설정합니다.
    }
}

// 예외 발생 시
MyException myException = new MyException("에러 메시지");
System.out.println(myException.getMessage()); // "에러 메시지" 출력

 

5. 어떤 방식으로 코드를 작성하는게 선호될까?

첫 번째 방식: @AllArgsConstructor 사용

  • 이 방식은 RecipeApplicationException 클래스에서 사용된다. 이 클래스는 ErrorCode 타입의 errorCode 필드 하나만을 가지고 있을 때 이 방식이 적용된다. Lombok 라이브러리의 @AllArgsConstructor 어노테이션을 사용하면, 자동으로 모든 필드를 매개변수로 받는 생성자가 생성된다. 이 경우, errorCode 필드만을 매개변수로 받는 생성자가 만들어지는 것이다.

  • 이 생성자는 RuntimeException의 message 필드를 직접 초기화하지 않기 때문에, 예외 메시지를 얻기 위해서는 getErrorCode().getMessage()를 호출해야 한다. 이는 RecipeApplicationException의 errorCode 필드에서 메시지를 직접 가져오는 방식이다.
@Getter
@AllArgsConstructor
public class RecipeApplicationException extends RuntimeException {

	private ErrorCode errorCode;
	
}

 

두 번째 방식: 명시적 생성자 사용

  • 이 방식은 RuntimeException의 기본 기능을 더 활용하고자 할 때 사용된다. 특히 message 필드를 활용하기 위해 쓰인다. 이 방식에서는 생성자를 명시적으로 정의하고, 생성자 내에서 super(errorCode.getMessage())를 호출한다. 이 호출은 RuntimeException의 생성자를 호출하는 것으로, 이때 message 필드가 errorCode.getMessage()로 초기화된다.

  • 이 방식의 주요 특징은 Java의 표준 예외 처리 메커니즘과의 통합성이 뛰어나다는 것이다. 예외 객체에 .getMessage()를 호출하면, RuntimeException의 message 필드에서 바로 예외 메시지를 가져올 수 있다. 따라서, 별도의 메서드 호출 없이도 예외 메시지에 쉽게 접근할 수 있게 된다.
@Getter
public class RecipeApplicationException extends RuntimeException {

	private ErrorCode errorCode;

	public RecipeApplicationException(ErrorCode errorCode) {
		super(errorCode.getMessage()); // 여기에서 예외 메시지를 설정합니다.
		this.errorCode = errorCode;
	}

}

 

결론

  1. 첫 번째 방식의 사용 시점
    • 클래스가 간단한 구조를 가질 때 유용하다. 하지만 예외 메시지에 바로 접근하려면 추가적인 메서드 호출이 필요하다. 이 방식은 구조가 간단하고, 코드가 직관적일 때 좋은 선택이다. 하지만 예외 메시지에 즉시 접근하고자 한다면, 이 방식은 약간의 불편함을 가져올 수 있다.

  2. 두 번째 방식의 이점
    • Java의 표준 예외 처리 시스템과의 통합성이 뛰어나며, 예외 메시지에 바로 접근할 수 있는 편의성을 제공한다. 이 방식은 기존 Java 예외 처리 코드와의 호환성을 높여주고, 코드의 가독성 및 일관성을 향상시킨다. 예외 처리 로직을 더 명확하고 일관되게 관리할 수 있어, 개발자들 사이에서 선호되는 경향이 있다.

 

 

반응형