스프링 부트 핵심 가이드 - 에러와 예외 처리

이건희·2024년 3월 21일
0

이번 장에서 유효성 검사 등 여러가지를 다뤘었지만 나는 예외 처리에 집중하여 포스팅 해보겠다. 개발을 진행하면서 커스텀 예외를 어떻게 만들고 사용할지에 대해 항상 고민했었는데, 이 책이 아주 잘 설명해주었다. 그래서 한번 확실히 정리하려고 한다.

예외와 에러

예외

예외(Exception)란 입력 값의 처리가 불가능하거나 참조된 값이 잘못된 경우 등 애플리케이션이 정상적으로 동작하지 못하는 상황을 의미한다. 예외는 개발자가 직접 처리할 수 있는 것이므로 미리 코드 설계를 통해 처리할 수 있다.

에러

에러는 주로 자바의 가상머신에서 발생시키는 것으로 어플리케이션 코드에서 거의 처리할 수 없다. 예를 들어 StackOverFlow, OutOfMemory 등이 있다. 이는 발생 시점에서 처리하는게 아니라 문제가 발생하지 않도록 원천적으로 차단해야 한다.

예외 클래스

  • 모든 예외 클래스는 Throwable 클래스를 상속 받는다.

  • 예외들은 Checked ExceptionUnchecked Exception으로 구분할 수 있다.

Checked ExceptionUnchecked Exception
처리 여부반드시 예외 처리 필요명시적 처리를 강제하지 않음
확인 시점컴파일 단계실행 중 단계
대표적인 예외 클래스IOException, SQLExceptionRuntimeException, NullPointException, IllegalArgumentException, IdenxOutOfBoundException, SystemException
분류RuntimeException 상속 받지 않음RuntimeException을 상속 받음

CheckedUnchecked의 가장 큰 차이점은 컴파일 단계에서 확인 가능하냐, Runtime에서 확인 가능하냐의 차이이다. 즉, Unchecked의 경우에는 문법상 문제는 없지만 프로그램이 동작하는 도중 예기치 않은 상황이 생겨 발생하는 예외를 의미한다.


Throw와 Throws

책에서는 자세히 다루지 않은 내용이지만 궁금해서 찾아보았다.

Throw

Throw개발자가 의도적으로 예외를 발생시킬 때 사용한다.

한번 예시를 보자

public void updateMemberName(String memberId, String name) {
	Optional<Member> member = memberRepository.findById(memberId)
    if(member.isEmpty()) {
    	throw new MemberNotFoundException()
    }
    member.setName(name);
}

위와 같이 개발자가 직접 원하는 타이밍에 예외를 던지게 해준다.

Throws

Throws해당 메서드 내에서 예외처리를 하지 않고, 메서드 호출부로 예외를 전가한다. 즉, 호출부에서 예외처리를 하도록 던진다.

한번 예시를 보자.

public class FileExample {

    public static void main(String[] args) {
        try {
            readFile("example.txt");
        } catch (FileNotFoundException e) {
            System.out.println("파일을 찾을 수 없습니다: " + e.getMessage());
        }
    }

    public static void readFile(String filename) throws FileNotFoundException {
        File file = new File(filename);
        Scanner scanner = new Scanner(file); // 파일을 열 때 FileNotFoundException이 발생할 수 있음
        while (scanner.hasNextLine()) {
            String line = scanner.nextLine();
            System.out.println(line);
        }
        scanner.close();
    }

위 코드는 readFile 메서드에서 throws FileNotFoundException을 작성하여 예외 발생 시, 호출부로 예외를 전가하고 있다. new Scanner(file)에서 예외 발생 시, main 메서드로 예외가 전가되고, 이는 maincatch 부분에서 예외를 처리한다.


스프링 부트의 예외 처리 방식

보통 웹 서비스에서는 예외가 발생하면 요청을 보낸 클라이언트에 어떤 문제가 발생했는지 상황을 전달하는 경우가 많다.

예외가 발생했을 때 클라이언트에 오류 메세지를 전달하려면 각 레이어에서 발생한 예외를 컨트롤러로 전달해야 한다. 이렇게 전달받은 예외를 스프링 부트에서 처리하는 방식으로 크게 두가지가 있다.

@(Rest)ControllerAdvice와 @ExceptionHandler

우선 코드를 보면서 해석해보자.

CustomExceptionHandler

@RestControllerAdvice 
public class CustomExceptionHandler {
	
    @ExceptionHandler(value = RuntimeException.class)
    public ResponseEntity<Map<String, String>> handleException(RuntimeException e, HttpServletRequest request) {
    	HttpHeaders responseheaders = new HttpHeaders();
        HttpStatus httpStatus = HttpStatus.BAD_REQUEST;
        
        Map<String, String> map = new HashMap<>();
        map.put("error type", httpStatus.getResponsePhrase());
        map.put("code", "400");
        map.put("message", e.getMessage());
        
        return new ResponseEntity<>(map, responseHeaders, httpStatus);
        
    }
}
  • @RestControllerAdvice@ControllerAdvice@Controller, @RestController에서 발생하는 예외를 한 곳에서 관리하고 처리할 수 있게 하는 기능을 수행한다.

  • @ExceptionHandler@Controller, @RestController적용된 빈에서 발생하는 예외를 잡아 처리하는 메서드를 정의할 때 사용한다.

  • @(Rest)Controller가 붙은 클래스에서 발생하는 RuntimeException은 위 코드에서 처리된다.

  • 또한 @(Rest)Controller는 범위를 지정할 수 있고, @ExceptionHandler는 배열 형식으로 예외를 받을 수 있다.

  • @RestControllerAdvice@ControllerAdvice는 응답을 JSON 형태로 보내느냐의 차이이다.

만약 특정 컨트롤러 클래스 내에 @ExceptionHandler를 사용한 메서드를 선언하면 해당 클래스에 국한해서 예외처리를 할 수 있다.


커스텀 예외

자바에서도 많은 예외를 제공하지만, 예외의 타입과 이름만으로는 해당 예외가 어떤 예외인지 인지하기 어려운 경우가 많다. 이런 경우에는 개발자가 커스텀 예외를 만들어 사용할 수 있다.

이번에 만들어볼 커스텀 예외 클래스를 생성하는 데 필요한 내용은 다음과 같다.

  • Error Type: HttpStatus의 reason phrase

  • Error Code: HttpStatus의 Code

  • message: 상황 별 상세 메세지

우선 코드를 보고 살펴보자.

ExceptionClass 열거형

public class Constants {

	public enum ExceptionClass {
    
    	PRODUCT("Product");
        
        private String exceptionClass;
        
        
        
        ExceptionClass(String exceptionClass) {
        	this.exceptionClass = exceptionClass;
        }
        
        public String getExceptionClass() {
        	return exceptionClass;
        }
        
        @Override
        public String toString() {
        	return getExceptionClass() + " Exception. ";
        }
    }
}
  • ExceptionClass라는 열거형은 커스텀 예외 클래스에서 메세지 내부에 어떤 도메인에서 문제가 발생했는지 보여주는데 사용된다.

  • 위 코드에서는 Product 도메인을 예시로 작성하였다.

CustomException

@Getter
public class CustomException extends Exception {

	private Constants.ExceptionClass exceptionClass;
    private HttpStatus httpStatus;
    
    public CustomException(Constants.ExceptionClass exceptionClass, HttpStatus httpStatus, String message) {
    	super(exceptionClass.toString() + message);
        //super: Exception의 생성자 호출 -> Exception은 Throwable을 상속 받고, Excpetion의 생성자도 Throwable의 생성자 호출
    	this.exceptionClass = exceptionClass;
    	this.httpStatus = httpStatus);
	}
}

CustomException을 처리하는 handleException 메서드

@ExceptionHandler(value = CustomException.class)
public ResponseEntity<Map<String, String>> handleException(CustomExcpetion e, HttpServletRequest request) {

	HttpHeaders responseHeaders = new HttpHeaders();
    
    Map<String, String> map = new HashMap<>();
	map.put("error type", e.getHttpStatusType());
    map.put("code", Integer.toString(e.getHttpStatusCode()));
    map.put("message", e.getMessage());
    
    return new ResponseEntity<>(map, responseHeaders, e.getHttpStatus());
}

예외를 발생시키는 Controller

@GetMapping("/custom")
public void getCustomException() throws CustomException {
	throw new CustomException(ExceptionClass.PRODUCT, HttpStatus.BAD_REQUEST, "getCustomException 호출");
}

코드를 살펴보자면,

  • /custom으로 GET 요청을 할 시, CustomException을 던짐

    • CustomException은 인자로 ExceptionClass 열거형, HttpStatus, Message를 받음
  • @ExceptionHandler는 CustomException이 발생하면 처리를 담당

    • CustomException 발생 시, 인자로 받은 ExceptionClass 열거형, HttpStatus, Message를 이용해 클라이언트 응답을 형성

이렇게 커스텀 예외를 만들 수 있고, 원할 시 다른 응답 필드 등을 설정할 수 있다.

profile
백엔드 개발자가 되겠어요

0개의 댓글