BackEnd 3 - Spring Boot, Global Exception

eve·2025년 12월 17일

✳️ Global Exception Handler

  • Spring MVC 프레임워크에서 제공하는 기능으로, 애플리케이션 전반에 걸쳐 발생하는 예외를 "중앙 집중식"으로 처리할 수 있는 매커니즘이다.
  • 전역 예외 처리기를 사용하면, 각 컨트롤러에서 발생하는 예외를 개별 처리하는 대신, 모든 예외를 한 곳에서 처리하게 되어 코드의 중복을 줄이고, 일관성 있는 예외처리 로직을 구현할 수 있다.

주요 구성 요소

  • @ControllerAdvice / @RestControllerAdvice
    :Spring MVC 컨트롤러에서 발생하는 예외를 전역적으로 처리하기 위한 클래스에 지정한다.
    이 어노테이션이 지정된 클래스는 모든 컨트롤러에서 발생하는 예외를 잡아, 처리할 수 있다.

  • @ExceptionHandler
    : 특정 예외 유형에 대한 처리를 지정된 메서드에 할당한다.
    이 어노테이션이 지정된 메서드는 해당 예외가 발생했을 때 호출되어, 적절히 처리하고 클라이언트에 응답을 반환한다.

  • 일반적으로 HTTP 응답 상태 코드만으로는 클라이언트가 오류의 세부 사항을 알기는 어렵다.

  • GlobalException 클래스는 예외 발생 시, 해당 예외에 대한 커스텀 오류 코드를 생성하고, 이를 HTTP 응답에 포함시킨다.

Handler 예제

@ControllerAdvice
public class GlobalExceptionHandler {
	
    @ExceptionHandler(SpecificException.class)
	public ModelAndView handleSpecificException(SpecificException ex) {
		
        ModelAndView mav = new ModelAndView();
		mav.addObject("errorMessage", ex.getMessage());
		mav.setViewName("errorPage");
		
        return mav;
        
	}
}
@ControllerAdvice

@RestControllerAdvice
public class GlobalExceptionHandler {

	@ExceptionHandler(SpecificException.class)
	public ResponseEntity<ApiError> handleSpecificException(SpecificException ex) {

		ApiError apiError = new ApiError(HttpStatus.BAD_REQUEST, ex.getMessage(), "에러 상세 설명");
		return new ResponseEntity<>(apiError, apiError.getStatus());
	
    }
    
    // IllegalArgumentException 예외 처리
	@ExceptionHandler(IllegalArgumentException.class)
	@ResponseStatus(HttpStatus.BAD_REQUEST) // HTTP 400 상태 반환
	public ResponseEntity<String> handleIllegalArgumentException(IllegalArgumentException ex) {

		return ResponseEntity
			.status(HttpStatus.BAD_REQUEST)
			.body(ex.getMessage());
	}
}
@RestControllerAdvice
  • @ExceptionHandler(IllegalArgumentException.class)
    : IllegalArgumentException이 발생했을 때 호출되는 메서드. 예외를 처리하고 지정된 응답을 반환한다.

  • @ResponseStatus(HttpStatus.BAD_REQUEST)
    : 예외 처리 후 반환할 HTTP 상태 코드를 지정한다. 여기서는 400 Bad Request 반환

  • HTTP 응답 생성
    : ResponseEntity 객체를 사용해, 예외 메시지를 포함한 HTTP 응답을 생성하고, 상태 코드를 400 Bad Request로 설정하여 반환한다.


✳️ 커스텀 예외 클래스

: 여러 개의 커스텀 예외 클래스가 사용될 때,
이러한 예외들은 특정 상황이나 오류를 명확하게 표현하고 처리하기 위해 정의된다.

각 예외 클래스는 특정 조건에서 발생하며, 코드의 가독성과 유지보수성을 높이는 데 도움을 준다.

*️⃣ 커스텀 예외 클래스 목적과 역할

  • 코드 가독성 향상

    • 코드에서 일반적인 RuntimeException이나, Exception 대신에 커스텀 예외를 사용함으로써,
      예외가 발생한 상황을 더 명확하게 전달 가능하다.
    • 예로, throw new BookNotFoundException("해당 책을 찾을 수 없습니다.); 와 같이 구체적인 예외를 던지면, 코드 리뷰나 유지보수 시에도 쉽게 이해할 수 있다.
  • 예외 처리의 일관성 유지

    • 전역 예외 처리기(GlobalExceptionHandler)에서 각 커스텀 예외를 처리함으로써, 애플리케이션 전체에서 일관된 방식으로 오류를 관리할 수 있다.
      예로, 모든 BookNotFoundException 예외를 404 Not Found 상태 코드로 처리하도록 설정할 수 있다.

*️⃣ 커스텀 예외 클래스 예시

  • 롤백이 필요할 때 발생하는 예외
public class TransactionRollbackException extends RuntimeException {
	public TransactionRollbackException(String message) {
		super(message);
	}
}

  • 책을 찾을 수 없을 때 발생하는 예외
public class BookNotFoundException extends IllegalArgumentException {
	public BookNotFoundException(String message) {
		super(message);
	}
}

  • 삭제할 수 없는 책을 삭제하려 할 때 발생
public class BookDeletionException extends IllegalArgumentException {
	public BookDeletionException(String message) {
		super(message);
	}
}

  • 예외 처리기(Exception Handler)에 커스텀 예외 처리 적용
@ControllerAdvice
public class GlobalExceptionHandler {

	// TransactionRollbackException 처리
	@ExceptionHandler(TransactionRollbackException.class)
	@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) // HTTP 500 상태를 반환
	public ResponseEntity<String> handleTransactionRollbackException(TransactionRollbackException ex)
	{
		return ResponseEntity
			.status(HttpStatus.INTERNAL_SERVER_ERROR)
			.body(ex.getMessage());
	}
    
    // BookDeletionException 처리
	@ExceptionHandler(BookDeletionException.class)
	@ResponseStatus(HttpStatus.FORBIDDEN) // HTTP 403 상태를 반환
	public ResponseEntity<String> handleBookDeletionException(BookDeletionException ex) 
    {
		return ResponseEntity
			.status(HttpStatus.FORBIDDEN)
			.body(ex.getMessage());
	}

	// BookNotFoundException 처리
	@ExceptionHandler(BookNotFoundException.class)
	@ResponseStatus(HttpStatus.NOT_FOUND) // HTTP 404 상태를 반환
	public ResponseEntity<String> handleBookNotFoundException(BookNotFoundException ex) 
    {
		return ResponseEntity
			.status(HttpStatus.NOT_FOUND)
			.body(ex.getMessage());
	}
    
    // IllegalArgumentException 예외 처리
	@ExceptionHandler(IllegalArgumentException.class)
	@ResponseStatus(HttpStatus.BAD_REQUEST) // HTTP 400 상태 반환
	public ResponseEntity<String> handleIllegalArgumentException(IllegalArgumentException ex) 
    {
		return ResponseEntity
			.status(HttpStatus.BAD_REQUEST)
			.body(ex.getMessage());
	}
}

  • 기존 서비스에서의 예외 처리 - 커스텀으로 대체
@Service
@RequiredArgsConstructor
@Transactional
public class BookService {

	...
    
	public void updateBookPublisher(Book book, String publisher) {
		book.setPublisher(publisher);
		// 예외 발생
		if(true) {
			throw new TransactionRollbackException("트랜잭션이 롤백되지 않습니다.");
		}
		saveBook(book);
	}
    
    ...
    
    public void deleteBook(Long id) {
		Book book = findVerifiedBook(id);
		if(book.getStatus() == Book.Status.BORROWED) {
			throw new BookDeletionException("대출 중인 책은 삭제할 수 없습니다.");
		}
		bookRepository.delete(book);
	}
    
	...
    
	public Book findVerifiedBook(Long id) {
		return bookRepository.findById(id)
			.orElseThrow(() -> new BookNotFoundException("존재하지 않는 책입니다."));
	}
}

*️⃣ 커스텀 예외 적용

  • RuntimeException("트랜잭션이 롤백되지 않습니다.") → TransactionRollbackException("트랜잭션이 롤백되지 않습니다.")

  • IllegalArgumentException(대출 중인 책은 삭제할 수 없습니다."") → BookDeletionException("대출 중인 책은 삭제할 수 없습니다.")

  • IllegalArgumentException("존재하지 않는 책입니다.") → BookNotFoundException("존재하지 않는 책입니다.")


💡참고할 만한 강의들

0개의 댓글